2 * This file is part of the libCEC(R) library.
4 * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited. All rights reserved.
5 * libCEC(R) is an original work, containing original code.
7 * libCEC(R) is a trademark of Pulse-Eight Limited.
9 * This program is dual-licensed; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 * Alternatively, you can license this library under a commercial license,
25 * please contact Pulse-Eight Licensing for more information.
27 * For more information contact:
28 * Pulse-Eight Licensing <license@pulse-eight.com>
29 * http://www.pulse-eight.com/
30 * http://www.pulse-eight.net/
34 #include "USBCECAdapterCommunication.h"
36 #include "USBCECAdapterCommands.h"
37 #include "USBCECAdapterMessageQueue.h"
38 #include "USBCECAdapterMessage.h"
39 #include "lib/platform/sockets/serialport.h"
40 #include "lib/platform/util/timeutils.h"
41 #include "lib/platform/util/util.h"
42 #include "lib/platform/util/edid.h"
43 #include "lib/platform/adl/adl-edid.h"
44 #include "lib/platform/nvidia/nv-edid.h"
45 #include "lib/LibCEC.h"
46 #include "lib/CECProcessor.h"
50 using namespace PLATFORM
;
52 #define CEC_ADAPTER_PING_TIMEOUT 15000
53 #define CEC_ADAPTER_EEPROM_WRITE_INTERVAL 30000
54 #define CEC_ADAPTER_EEPROM_WRITE_RETRY 5000
57 #define CEC_LATEST_ADAPTER_FW_VERSION 2
58 // firmware date Thu Apr 26 20:14:49 2012 +0000
59 #define CEC_LATEST_ADAPTER_FW_DATE 0x4F99ACB9
61 #define LIB_CEC m_callback->GetLib()
63 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback
*callback
, const char *strPort
, uint16_t iBaudRate
/* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
64 IAdapterCommunication(callback
),
67 m_lastPollDestination(CECDEVICE_UNKNOWN
),
68 m_bInitialised(false),
71 m_adapterMessageQueue(NULL
),
72 m_iLastEepromWrite(0),
73 m_iScheduleEepromWrite(0)
75 m_logicalAddresses
.Clear();
76 for (unsigned int iPtr
= CECDEVICE_TV
; iPtr
< CECDEVICE_BROADCAST
; iPtr
++)
77 m_bWaitingForAck
[iPtr
] = false;
78 m_port
= new CSerialPort(strPort
, iBaudRate
);
79 m_commands
= new CUSBCECAdapterCommands(this);
82 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
85 DELETE_AND_NULL(m_commands
);
86 DELETE_AND_NULL(m_adapterMessageQueue
);
87 DELETE_AND_NULL(m_port
);
90 void CUSBCECAdapterCommunication::ResetMessageQueue(void)
92 DELETE_AND_NULL(m_adapterMessageQueue
);
93 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
94 m_adapterMessageQueue
->CreateThread();
97 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
99 bool bConnectionOpened(false);
101 CLockObject
lock(m_mutex
);
103 /* we need the port settings here */
106 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "port is NULL");
107 return bConnectionOpened
;
110 /* return true when the port is already open */
113 LIB_CEC
->AddLog(CEC_LOG_WARNING
, "port is already open");
119 /* try to open the connection */
121 CTimeout
timeout(iTimeoutMs
);
122 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
124 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
126 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
129 /* and retry every 250ms until the timeout passed */
132 /* return false when we couldn't connect */
133 if (!bConnectionOpened
)
135 LIB_CEC
->AddLog(CEC_LOG_ERROR
, strError
);
137 if (m_port
->GetErrorNumber() == EACCES
)
139 libcec_parameter param
;
140 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
141 param
.paramData
= (void*)"No permission to open the device";
142 LIB_CEC
->Alert(CEC_ALERT_PERMISSION_ERROR
, param
);
144 else if (m_port
->GetErrorNumber() == EBUSY
)
146 libcec_parameter param
;
147 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
148 param
.paramData
= (void*)"The serial port is busy. Only one program can access the device directly.";
149 LIB_CEC
->Alert(CEC_ALERT_PORT_BUSY
, param
);
154 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
158 // always start by setting the ackmask to 0, to clear previous values
159 cec_logical_addresses addresses
; addresses
.Clear();
160 SetLogicalAddresses(addresses
);
164 bConnectionOpened
= false;
165 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
167 else if (!bSkipChecks
&& !CheckAdapter())
169 bConnectionOpened
= false;
170 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
172 else if (bStartListening
)
174 /* start a ping thread, that will ping the adapter every 15 seconds
175 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
176 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
177 if (m_pingThread
->CreateThread())
179 bConnectionOpened
= true;
183 bConnectionOpened
= false;
184 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
188 if (!bConnectionOpened
|| !bStartListening
)
191 return bConnectionOpened
;
194 void CUSBCECAdapterCommunication::Close(void)
196 /* stop the reader thread */
199 CLockObject
lock(m_mutex
);
201 /* set the ackmask to 0 before closing the connection */
202 if (IsOpen() && m_port
->GetErrorNumber() == 0)
204 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - closing the connection", __FUNCTION__
);
205 cec_logical_addresses addresses
; addresses
.Clear();
206 SetLogicalAddresses(addresses
);
207 if (m_commands
->GetFirmwareVersion() >= 2)
208 SetControlledMode(false);
211 m_adapterMessageQueue
->Clear();
213 /* stop and delete the ping thread */
214 DELETE_AND_NULL(m_pingThread
);
216 /* close and delete the com port connection */
221 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
, bool UNUSED(bIsReply
))
223 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
227 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
229 /* mark as waiting for an ack from the destination */
230 MarkAsWaiting(data
.destination
);
232 /* send the message */
233 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
235 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
236 retVal
= output
->state
;
242 void *CUSBCECAdapterCommunication::Process(void)
244 CCECAdapterMessage msg
;
245 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread started");
247 bool bWriteEeprom(false);
250 /* read from the serial port */
251 if (!ReadFromDevice(50, 5))
253 libcec_parameter param
;
254 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
255 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
260 // check if we need to do another eeprom write
262 CLockObject
lock(m_mutex
);
263 uint64_t iNow
= GetTimeMs();
264 if (m_iScheduleEepromWrite
> 0 && m_iScheduleEepromWrite
>= iNow
)
266 m_iScheduleEepromWrite
= 0;
267 m_iLastEepromWrite
= iNow
;
274 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "updating the eeprom (scheduled)");
275 bWriteEeprom
= false;
276 if (!m_commands
->WriteEEPROM())
278 // failed, retry later
279 CLockObject
lock(m_mutex
);
280 m_iScheduleEepromWrite
= GetTimeMs() + CEC_ADAPTER_EEPROM_WRITE_RETRY
;
284 /* TODO sleep 5 ms so other threads can get a lock */
288 m_adapterMessageQueue
->Clear();
289 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread ended");
293 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
295 bool bIsError(msg
.IsError());
296 cec_adapter_messagecode
messageCode(msg
.Message());
297 CLockObject
lock(m_mutex
);
299 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
301 m_lastPollDestination
= msg
.Destination();
302 if (msg
.Destination() < CECDEVICE_BROADCAST
)
304 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
307 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
310 m_bWaitingForAck
[msg
.Destination()] = false;
313 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
315 /* hack to suppress warnings when an LG is polling */
316 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
317 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
323 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
325 /* mark as waiting for an ack from the destination */
326 if (dest
< CECDEVICE_BROADCAST
)
328 CLockObject
lock(m_mutex
);
329 m_bWaitingForAck
[dest
] = true;
333 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
335 CTimeout
timeout(iTimeout
);
337 ssize_t
iBytesRead(0);
338 bool bGotMsgEnd(true);
340 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
343 /* if something was received, wait for MSGEND */
344 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
345 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
349 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
352 bool bChanged(false);
354 /* only send the command if the timeout changed */
356 CLockObject
lock(m_mutex
);
357 bChanged
= (m_iLineTimeout
!= iTimeout
);
358 m_iLineTimeout
= iTimeout
;
362 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
367 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
369 CLockObject
adapterLock(m_mutex
);
372 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "error writing command '%s' to serial port '%s': the connection is closed", CCECAdapterMessage::ToString(message
->Message()), m_port
->GetName().c_str());
373 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
377 /* write the message */
378 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
380 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());
381 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
382 // this will trigger an alert in the reader thread
387 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
388 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
392 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
394 ssize_t
iBytesRead(0);
399 /* read from the serial port */
401 CLockObject
lock(m_mutex
);
405 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
407 if (m_port
->GetErrorNumber())
409 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
415 if (iBytesRead
< 0 || iBytesRead
> 256)
417 else if (iBytesRead
> 0)
419 /* add the data to the current frame */
420 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
426 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
428 if (!IsOpen() || !m_adapterMessageQueue
)
431 /* create the adapter message for this command */
432 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
433 output
->PushBack(MSGSTART
);
434 output
->PushEscaped((uint8_t)msgCode
);
435 output
->Append(params
);
436 output
->PushBack(MSGEND
);
438 /* write the command */
439 if (!m_adapterMessageQueue
->Write(output
))
441 // this will trigger an alert in the reader thread
442 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
448 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
449 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
451 /* if the controller reported that the command was rejected, and we didn't send the command
452 to set controlled mode, then the controller probably switched to auto mode. set controlled
454 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
456 if (SetControlledMode(true))
457 return SendCommand(msgCode
, params
, true);
464 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
467 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
469 /* try to ping the adapter */
471 unsigned iPingTry(0);
472 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
474 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
478 /* try to read the firmware version */
479 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
481 /* try to set controlled mode for v2+ firmwares */
482 unsigned iControlledTry(0);
483 bool bControlled(false);
484 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
486 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
489 bReturn
= bControlled
;
494 /* try to read the build date */
495 m_commands
->RequestBuildDate();
497 SetInitialised(bReturn
);
501 bool CUSBCECAdapterCommunication::IsOpen(void)
503 /* thread is not being stopped, the port is open and the thread is running */
504 return !IsStopped() && m_port
->IsOpen() && IsRunning();
507 std::string
CUSBCECAdapterCommunication::GetError(void) const
509 return m_port
->GetError();
512 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
514 CLockObject
lock(m_mutex
);
515 m_bInitialised
= bSetTo
;
518 bool CUSBCECAdapterCommunication::IsInitialised(void)
520 CLockObject
lock(m_mutex
);
521 return m_bInitialised
;
524 bool CUSBCECAdapterCommunication::StartBootloader(void)
526 if (m_port
->IsOpen() && m_commands
->StartBootloader())
534 bool CUSBCECAdapterCommunication::SetLogicalAddresses(const cec_logical_addresses
&addresses
)
537 CLockObject
lock(m_mutex
);
538 if (m_logicalAddresses
== addresses
)
542 if (IsOpen() && m_commands
->SetAckMask(addresses
.AckMask()))
544 CLockObject
lock(m_mutex
);
545 m_logicalAddresses
= addresses
;
549 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "couldn't change the ackmask: the connection is closed");
553 cec_logical_addresses
CUSBCECAdapterCommunication::GetLogicalAddresses(void)
555 cec_logical_addresses addresses
;
556 CLockObject
lock(m_mutex
);
557 addresses
= m_logicalAddresses
;
561 bool CUSBCECAdapterCommunication::PingAdapter(void)
563 return IsOpen() ? m_commands
->PingAdapter() : false;
566 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
568 return m_commands
? m_commands
->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN
;
571 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
573 return IsOpen() ? m_commands
->RequestBuildDate() : m_commands
? m_commands
->GetPersistedBuildDate() : 0;
576 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
578 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
&&
579 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
;
582 bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration
&configuration
)
586 // returns true when something changed
587 if (m_commands
->PersistConfiguration(configuration
))
590 CLockObject
lock(m_mutex
);
591 uint64_t iNow
= GetTimeMs();
592 if (iNow
- m_iLastEepromWrite
< CEC_ADAPTER_EEPROM_WRITE_INTERVAL
)
594 // if there was more than 1 write within the last 30 seconds, schedule another one
595 if (m_iScheduleEepromWrite
== 0)
596 m_iScheduleEepromWrite
= m_iLastEepromWrite
+ CEC_ADAPTER_EEPROM_WRITE_INTERVAL
;
601 m_iLastEepromWrite
= iNow
;
605 if (!m_commands
->WriteEEPROM())
607 // write failed, retry later
608 CLockObject
lock(m_mutex
);
609 m_iScheduleEepromWrite
= GetTimeMs() + CEC_ADAPTER_EEPROM_WRITE_RETRY
;
613 return IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
616 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
&configuration
)
618 return IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
621 std::string
CUSBCECAdapterCommunication::GetPortName(void)
623 return m_port
->GetName();
626 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
628 return IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
631 uint16_t CUSBCECAdapterCommunication::GetPhysicalAddress(void)
635 // try to get the PA from ADL
636 #if defined(HAS_ADL_EDID_PARSER)
638 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address via ADL", __FUNCTION__
);
640 iPA
= adl
.GetPhysicalAddress();
641 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - ADL returned physical address %04x", __FUNCTION__
, iPA
);
645 // try to get the PA from the nvidia driver
646 #if defined(HAS_NVIDIA_EDID_PARSER)
649 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address via nvidia driver", __FUNCTION__
);
651 iPA
= nv
.GetPhysicalAddress();
652 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - nvidia driver returned physical address %04x", __FUNCTION__
, iPA
);
656 // try to get the PA from the OS
659 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address from the OS", __FUNCTION__
);
660 iPA
= CEDIDParser::GetPhysicalAddress();
661 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - OS returned physical address %04x", __FUNCTION__
, iPA
);
667 void *CAdapterPingThread::Process(void)
671 if (m_timeout
.TimeLeft() == 0)
673 /* reinit the timeout */
674 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
676 /* send a ping to the adapter */
678 int iFailedCounter(0);
679 while (!bPinged
&& iFailedCounter
< 3)
681 if (!m_com
->PingAdapter())
683 /* sleep and retry */
684 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
693 if (iFailedCounter
== 3)
695 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
696 m_com
->LIB_CEC
->AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
697 m_com
->StopThread(false);