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/
33 #include "USBCECAdapterCommunication.h"
34 #include "USBCECAdapterCommands.h"
35 #include "USBCECAdapterMessageQueue.h"
36 #include "../platform/sockets/serialport.h"
37 #include "../platform/util/timeutils.h"
38 #include "../platform/util/util.h"
39 #include "../platform/util/edid.h"
40 #include "../platform/adl/adl-edid.h"
41 #include "../platform/nvidia/nv-edid.h"
42 #include "../LibCEC.h"
43 #include "../CECProcessor.h"
47 using namespace PLATFORM
;
49 #define CEC_ADAPTER_PING_TIMEOUT 15000
52 #define CEC_LATEST_ADAPTER_FW_VERSION 2
53 // firmware date Thu Apr 26 20:14:49 2012 +0000
54 #define CEC_LATEST_ADAPTER_FW_DATE 0x4F99ACB9
56 #define LIB_CEC m_callback->GetLib()
58 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback
*callback
, const char *strPort
, uint16_t iBaudRate
/* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
59 IAdapterCommunication(callback
),
62 m_lastPollDestination(CECDEVICE_UNKNOWN
),
63 m_bInitialised(false),
66 m_adapterMessageQueue(NULL
),
69 for (unsigned int iPtr
= CECDEVICE_TV
; iPtr
< CECDEVICE_BROADCAST
; iPtr
++)
70 m_bWaitingForAck
[iPtr
] = false;
71 m_port
= new CSerialPort(strPort
, iBaudRate
);
72 m_commands
= new CUSBCECAdapterCommands(this);
75 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
78 DELETE_AND_NULL(m_commands
);
79 DELETE_AND_NULL(m_adapterMessageQueue
);
80 DELETE_AND_NULL(m_port
);
83 void CUSBCECAdapterCommunication::ResetMessageQueue(void)
85 DELETE_AND_NULL(m_adapterMessageQueue
);
86 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
87 m_adapterMessageQueue
->CreateThread();
90 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
92 bool bConnectionOpened(false);
94 CLockObject
lock(m_mutex
);
96 /* we need the port settings here */
99 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "port is NULL");
100 return bConnectionOpened
;
103 /* return true when the port is already open */
106 LIB_CEC
->AddLog(CEC_LOG_WARNING
, "port is already open");
112 /* try to open the connection */
114 CTimeout
timeout(iTimeoutMs
);
115 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
117 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
119 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
122 /* and retry every 250ms until the timeout passed */
125 /* return false when we couldn't connect */
126 if (!bConnectionOpened
)
128 LIB_CEC
->AddLog(CEC_LOG_ERROR
, strError
);
130 if (m_port
->GetErrorNumber() == EACCES
)
132 libcec_parameter param
;
133 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
134 param
.paramData
= (void*)"No permission to open the device";
135 LIB_CEC
->Alert(CEC_ALERT_PERMISSION_ERROR
, param
);
137 else if (m_port
->GetErrorNumber() == EBUSY
)
139 libcec_parameter param
;
140 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
141 param
.paramData
= (void*)"The serial port is busy. Only one program can access the device directly.";
142 LIB_CEC
->Alert(CEC_ALERT_PORT_BUSY
, param
);
147 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
151 // always start by setting the ackmask to 0, to clear previous values
156 bConnectionOpened
= false;
157 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
159 else if (!bSkipChecks
&& !CheckAdapter())
161 bConnectionOpened
= false;
162 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
164 else if (bStartListening
)
166 /* start a ping thread, that will ping the adapter every 15 seconds
167 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
168 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
169 if (m_pingThread
->CreateThread())
171 bConnectionOpened
= true;
175 bConnectionOpened
= false;
176 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
180 if (!bConnectionOpened
|| !bStartListening
)
183 return bConnectionOpened
;
186 void CUSBCECAdapterCommunication::Close(void)
188 /* stop the reader thread */
191 CLockObject
lock(m_mutex
);
193 /* set the ackmask to 0 before closing the connection */
194 if (IsOpen() && m_port
->GetErrorNumber() == 0)
196 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - closing the connection", __FUNCTION__
);
198 if (m_commands
->GetFirmwareVersion() >= 2)
199 SetControlledMode(false);
202 m_adapterMessageQueue
->Clear();
204 /* stop and delete the ping thread */
205 DELETE_AND_NULL(m_pingThread
);
207 /* close and delete the com port connection */
212 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
)
214 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
218 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
220 /* mark as waiting for an ack from the destination */
221 MarkAsWaiting(data
.destination
);
223 /* send the message */
224 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
226 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
227 retVal
= output
->state
;
233 void *CUSBCECAdapterCommunication::Process(void)
235 CCECAdapterMessage msg
;
236 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread started");
240 /* read from the serial port */
241 if (!ReadFromDevice(50, 5))
243 libcec_parameter param
;
244 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
245 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
250 /* TODO sleep 5 ms so other threads can get a lock */
254 m_adapterMessageQueue
->Clear();
255 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread ended");
259 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
261 bool bIsError(msg
.IsError());
262 cec_adapter_messagecode
messageCode(msg
.Message());
263 CLockObject
lock(m_mutex
);
265 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
267 m_lastPollDestination
= msg
.Destination();
268 if (msg
.Destination() < CECDEVICE_BROADCAST
)
270 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
273 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
276 m_bWaitingForAck
[msg
.Destination()] = false;
279 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
281 /* hack to suppress warnings when an LG is polling */
282 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
283 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
289 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
291 /* mark as waiting for an ack from the destination */
292 if (dest
< CECDEVICE_BROADCAST
)
294 CLockObject
lock(m_mutex
);
295 m_bWaitingForAck
[dest
] = true;
299 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
301 CTimeout
timeout(iTimeout
);
303 ssize_t
iBytesRead(0);
304 bool bGotMsgEnd(true);
306 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
309 /* if something was received, wait for MSGEND */
310 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
311 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
315 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
318 bool bChanged(false);
320 /* only send the command if the timeout changed */
322 CLockObject
lock(m_mutex
);
323 bChanged
= (m_iLineTimeout
!= iTimeout
);
324 m_iLineTimeout
= iTimeout
;
328 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
333 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
335 CLockObject
adapterLock(m_mutex
);
338 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());
339 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
343 /* write the message */
344 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
346 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());
347 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
348 // this will trigger an alert in the reader thread
353 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
354 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
358 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
360 ssize_t
iBytesRead(0);
365 /* read from the serial port */
367 CLockObject
lock(m_mutex
);
371 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
373 if (m_port
->GetErrorNumber())
375 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
381 if (iBytesRead
< 0 || iBytesRead
> 256)
383 else if (iBytesRead
> 0)
385 /* add the data to the current frame */
386 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
392 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
394 if (!IsOpen() || !m_adapterMessageQueue
)
397 /* create the adapter message for this command */
398 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
399 output
->PushBack(MSGSTART
);
400 output
->PushEscaped((uint8_t)msgCode
);
401 output
->Append(params
);
402 output
->PushBack(MSGEND
);
404 /* write the command */
405 if (!m_adapterMessageQueue
->Write(output
))
407 // this will trigger an alert in the reader thread
408 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
414 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
415 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
417 /* if the controller reported that the command was rejected, and we didn't send the command
418 to set controlled mode, then the controller probably switched to auto mode. set controlled
420 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
422 if (SetControlledMode(true))
423 return SendCommand(msgCode
, params
, true);
430 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
433 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
435 /* try to ping the adapter */
437 unsigned iPingTry(0);
438 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
440 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
444 /* try to read the firmware version */
445 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
447 /* try to set controlled mode for v2+ firmwares */
448 unsigned iControlledTry(0);
449 bool bControlled(false);
450 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
452 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
455 bReturn
= bControlled
;
460 /* try to read the build date */
461 m_commands
->RequestBuildDate();
463 SetInitialised(bReturn
);
467 bool CUSBCECAdapterCommunication::IsOpen(void)
469 /* thread is not being stopped, the port is open and the thread is running */
470 return !IsStopped() && m_port
->IsOpen() && IsRunning();
473 CStdString
CUSBCECAdapterCommunication::GetError(void) const
475 return m_port
->GetError();
478 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
480 CLockObject
lock(m_mutex
);
481 m_bInitialised
= bSetTo
;
484 bool CUSBCECAdapterCommunication::IsInitialised(void)
486 CLockObject
lock(m_mutex
);
487 return m_bInitialised
;
490 bool CUSBCECAdapterCommunication::StartBootloader(void)
492 if (m_port
->IsOpen() && m_commands
->StartBootloader())
500 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask
)
503 CLockObject
lock(m_mutex
);
504 if (m_iAckMask
== iMask
)
508 if (IsOpen() && m_commands
->SetAckMask(iMask
))
510 CLockObject
lock(m_mutex
);
515 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "couldn't change the ackmask: the connection is closed");
519 uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
521 CLockObject
lock(m_mutex
);
525 bool CUSBCECAdapterCommunication::PingAdapter(void)
527 return IsOpen() ? m_commands
->PingAdapter() : false;
530 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
532 return m_commands
? m_commands
->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN
;
535 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
537 return IsOpen() ? m_commands
->RequestBuildDate() : m_commands
? m_commands
->GetPersistedBuildDate() : 0;
540 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
542 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
&&
543 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
;
546 bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration
&configuration
)
548 return IsOpen() ? m_commands
->PersistConfiguration(configuration
) : false;
551 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
&configuration
)
553 return IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
556 CStdString
CUSBCECAdapterCommunication::GetPortName(void)
558 return m_port
->GetName();
561 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
563 return IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
566 uint16_t CUSBCECAdapterCommunication::GetPhysicalAddress(void)
570 // try to get the PA from ADL
571 #if defined(HAS_ADL_EDID_PARSER)
573 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address via ADL", __FUNCTION__
);
575 iPA
= adl
.GetPhysicalAddress();
576 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - ADL returned physical address %04x", __FUNCTION__
, iPA
);
580 // try to get the PA from the nvidia driver
581 #if defined(HAS_NVIDIA_EDID_PARSER)
584 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address via nvidia driver", __FUNCTION__
);
586 iPA
= nv
.GetPhysicalAddress();
587 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - nvidia driver returned physical address %04x", __FUNCTION__
, iPA
);
591 // try to get the PA from the OS
594 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address from the OS", __FUNCTION__
);
595 iPA
= CEDIDParser::GetPhysicalAddress();
596 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - OS returned physical address %04x", __FUNCTION__
, iPA
);
602 void *CAdapterPingThread::Process(void)
606 if (m_timeout
.TimeLeft() == 0)
608 /* reinit the timeout */
609 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
611 /* send a ping to the adapter */
613 int iFailedCounter(0);
614 while (!bPinged
&& iFailedCounter
< 3)
616 if (!m_com
->PingAdapter())
618 /* sleep and retry */
619 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
628 if (iFailedCounter
== 3)
630 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
631 m_com
->LIB_CEC
->AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
632 m_com
->StopThread(false);