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 Aug 2 08:31:24 UTC 2012
59 #define CEC_LATEST_ADAPTER_FW_DATE 0x501a4b0c
61 #define CEC_FW_DATE_EXTENDED_RESPONSE 0x501a4b0c
63 #define LIB_CEC m_callback->GetLib()
65 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback
*callback
, const char *strPort
, uint16_t iBaudRate
/* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
66 IAdapterCommunication(callback
),
69 m_lastPollDestination(CECDEVICE_UNKNOWN
),
70 m_bInitialised(false),
72 m_eepromWriteThread(NULL
),
74 m_adapterMessageQueue(NULL
)
76 m_logicalAddresses
.Clear();
77 for (unsigned int iPtr
= CECDEVICE_TV
; iPtr
< CECDEVICE_BROADCAST
; iPtr
++)
78 m_bWaitingForAck
[iPtr
] = false;
79 m_port
= new CSerialPort(strPort
, iBaudRate
);
80 m_commands
= new CUSBCECAdapterCommands(this);
83 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
86 DELETE_AND_NULL(m_commands
);
87 DELETE_AND_NULL(m_adapterMessageQueue
);
88 DELETE_AND_NULL(m_port
);
91 void CUSBCECAdapterCommunication::ResetMessageQueue(void)
93 DELETE_AND_NULL(m_adapterMessageQueue
);
94 m_adapterMessageQueue
= new CCECAdapterMessageQueue(this);
95 m_adapterMessageQueue
->CreateThread();
98 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks
/* = false */, bool bStartListening
/* = true */)
100 bool bConnectionOpened(false);
102 CLockObject
lock(m_mutex
);
104 /* we need the port settings here */
107 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "port is NULL");
108 return bConnectionOpened
;
111 /* return true when the port is already open */
114 LIB_CEC
->AddLog(CEC_LOG_WARNING
, "port is already open");
120 /* try to open the connection */
122 CTimeout
timeout(iTimeoutMs
);
123 while (!bConnectionOpened
&& timeout
.TimeLeft() > 0)
125 if ((bConnectionOpened
= m_port
->Open(timeout
.TimeLeft())) == false)
127 strError
.Format("error opening serial port '%s': %s", m_port
->GetName().c_str(), m_port
->GetError().c_str());
130 /* and retry every 250ms until the timeout passed */
133 /* return false when we couldn't connect */
134 if (!bConnectionOpened
)
136 LIB_CEC
->AddLog(CEC_LOG_ERROR
, strError
);
138 if (m_port
->GetErrorNumber() == EACCES
)
140 libcec_parameter param
;
141 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
142 param
.paramData
= (void*)"No permission to open the device";
143 LIB_CEC
->Alert(CEC_ALERT_PERMISSION_ERROR
, param
);
145 else if (m_port
->GetErrorNumber() == EBUSY
)
147 libcec_parameter param
;
148 param
.paramType
= CEC_PARAMETER_TYPE_STRING
;
149 param
.paramData
= (void*)"The serial port is busy. Only one program can access the device directly.";
150 LIB_CEC
->Alert(CEC_ALERT_PORT_BUSY
, param
);
155 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
159 // always start by setting the ackmask to 0, to clear previous values
160 cec_logical_addresses addresses
; addresses
.Clear();
161 SetLogicalAddresses(addresses
);
165 bConnectionOpened
= false;
166 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a communication thread");
168 else if (!bSkipChecks
&& !CheckAdapter())
170 bConnectionOpened
= false;
171 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter failed to pass basic checks");
173 else if (bStartListening
)
175 /* start the eeprom write thread, that handles all eeprom writes async */
176 m_eepromWriteThread
= new CAdapterEepromWriteThread(this);
177 if (!m_eepromWriteThread
->CreateThread())
179 bConnectionOpened
= false;
180 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create the eeprom write thread");
184 /* start a ping thread, that will ping the adapter every 15 seconds
185 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
186 m_pingThread
= new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT
);
187 if (m_pingThread
->CreateThread())
189 bConnectionOpened
= true;
193 bConnectionOpened
= false;
194 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "could not create a ping thread");
199 if (!bConnectionOpened
|| !bStartListening
)
202 return bConnectionOpened
;
205 void CUSBCECAdapterCommunication::Close(void)
207 /* stop the reader thread */
210 CLockObject
lock(m_mutex
);
212 /* set the ackmask to 0 before closing the connection */
213 if (IsOpen() && m_port
->GetErrorNumber() == 0)
215 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - closing the connection", __FUNCTION__
);
216 cec_logical_addresses addresses
; addresses
.Clear();
217 SetLogicalAddresses(addresses
);
218 if (m_commands
->GetFirmwareVersion() >= 2)
219 SetControlledMode(false);
222 m_adapterMessageQueue
->Clear();
224 /* stop and delete the write thread */
225 m_eepromWriteThread
->Stop();
226 DELETE_AND_NULL(m_eepromWriteThread
);
228 /* stop and delete the ping thread */
229 DELETE_AND_NULL(m_pingThread
);
231 /* close and delete the com port connection */
236 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
, bool UNUSED(bIsReply
))
238 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
242 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
244 /* mark as waiting for an ack from the destination */
245 MarkAsWaiting(data
.destination
);
247 /* send the message */
248 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
250 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
251 retVal
= output
->state
;
257 void *CUSBCECAdapterCommunication::Process(void)
259 CCECAdapterMessage msg
;
260 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread started");
264 /* read from the serial port */
265 if (!ReadFromDevice(50, 5))
267 libcec_parameter param
;
268 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
269 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
274 /* TODO sleep 5 ms so other threads can get a lock */
278 m_adapterMessageQueue
->Clear();
279 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread ended");
283 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
285 bool bIsError(msg
.IsError());
286 cec_adapter_messagecode
messageCode(msg
.Message());
287 CLockObject
lock(m_mutex
);
289 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
291 m_lastPollDestination
= msg
.Destination();
292 if (msg
.Destination() < CECDEVICE_BROADCAST
)
294 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
297 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
300 m_bWaitingForAck
[msg
.Destination()] = false;
303 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
305 /* hack to suppress warnings when an LG is polling */
306 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
307 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
313 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
315 /* mark as waiting for an ack from the destination */
316 if (dest
< CECDEVICE_BROADCAST
)
318 CLockObject
lock(m_mutex
);
319 m_bWaitingForAck
[dest
] = true;
323 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
325 CTimeout
timeout(iTimeout
);
327 ssize_t
iBytesRead(0);
328 bool bGotMsgEnd(true);
330 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
333 /* if something was received, wait for MSGEND */
334 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
335 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
339 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
342 bool bChanged(false);
344 /* only send the command if the timeout changed */
346 CLockObject
lock(m_mutex
);
347 bChanged
= (m_iLineTimeout
!= iTimeout
);
348 m_iLineTimeout
= iTimeout
;
352 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
357 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
359 CLockObject
adapterLock(m_mutex
);
362 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());
363 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
367 /* write the message */
368 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
370 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());
371 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
372 // this will trigger an alert in the reader thread
377 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
378 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
382 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
384 ssize_t
iBytesRead(0);
389 /* read from the serial port */
391 CLockObject
lock(m_mutex
);
395 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
397 if (m_port
->GetErrorNumber())
399 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
405 if (iBytesRead
< 0 || iBytesRead
> 256)
407 else if (iBytesRead
> 0)
409 /* add the data to the current frame */
410 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
416 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
418 if (!IsOpen() || !m_adapterMessageQueue
)
421 /* create the adapter message for this command */
422 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
423 output
->PushBack(MSGSTART
);
424 output
->PushEscaped((uint8_t)msgCode
);
425 output
->Append(params
);
426 output
->PushBack(MSGEND
);
428 /* write the command */
429 if (!m_adapterMessageQueue
->Write(output
))
431 // this will trigger an alert in the reader thread
432 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
438 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
439 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
441 /* if the controller reported that the command was rejected, and we didn't send the command
442 to set controlled mode, then the controller probably switched to auto mode. set controlled
444 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
446 if (SetControlledMode(true))
447 return SendCommand(msgCode
, params
, true);
454 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
457 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
459 /* try to ping the adapter */
461 unsigned iPingTry(0);
462 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
464 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
468 /* try to read the firmware version */
469 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
471 /* try to set controlled mode for v2+ firmwares */
472 unsigned iControlledTry(0);
473 bool bControlled(false);
474 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
476 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
479 bReturn
= bControlled
;
484 /* try to read the build date */
485 m_commands
->RequestBuildDate();
487 SetInitialised(bReturn
);
491 bool CUSBCECAdapterCommunication::IsOpen(void)
493 /* thread is not being stopped, the port is open and the thread is running */
494 return !IsStopped() && m_port
->IsOpen() && IsRunning();
497 std::string
CUSBCECAdapterCommunication::GetError(void) const
499 return m_port
->GetError();
502 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
504 CLockObject
lock(m_mutex
);
505 m_bInitialised
= bSetTo
;
508 bool CUSBCECAdapterCommunication::IsInitialised(void)
510 CLockObject
lock(m_mutex
);
511 return m_bInitialised
;
514 bool CUSBCECAdapterCommunication::StartBootloader(void)
516 if (m_port
->IsOpen() && m_commands
->StartBootloader())
524 bool CUSBCECAdapterCommunication::SetLogicalAddresses(const cec_logical_addresses
&addresses
)
527 CLockObject
lock(m_mutex
);
528 if (m_logicalAddresses
== addresses
)
532 if (IsOpen() && m_commands
->SetAckMask(addresses
.AckMask()))
534 CLockObject
lock(m_mutex
);
535 m_logicalAddresses
= addresses
;
539 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "couldn't change the ackmask: the connection is closed");
543 cec_logical_addresses
CUSBCECAdapterCommunication::GetLogicalAddresses(void)
545 cec_logical_addresses addresses
;
546 CLockObject
lock(m_mutex
);
547 addresses
= m_logicalAddresses
;
551 bool CUSBCECAdapterCommunication::PingAdapter(void)
553 return IsOpen() ? m_commands
->PingAdapter() : false;
556 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
558 return m_commands
? m_commands
->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN
;
561 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
563 uint32_t iBuildDate(0);
565 iBuildDate
= m_commands
->GetPersistedBuildDate();
566 if (iBuildDate
== 0 && IsOpen())
567 iBuildDate
= m_commands
->RequestBuildDate();
572 bool CUSBCECAdapterCommunication::ProvidesExtendedResponse(void)
574 uint32_t iBuildDate(0);
576 iBuildDate
= m_commands
->GetPersistedBuildDate();
578 return iBuildDate
>= CEC_FW_DATE_EXTENDED_RESPONSE
;
581 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
583 return GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
&&
584 GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
;
587 bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration
&configuration
)
590 m_commands
->PersistConfiguration(configuration
) && m_eepromWriteThread
->Write() :
594 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
&configuration
)
596 return IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
599 std::string
CUSBCECAdapterCommunication::GetPortName(void)
601 return m_port
->GetName();
604 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
606 return IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
609 uint16_t CUSBCECAdapterCommunication::GetPhysicalAddress(void)
613 // try to get the PA from ADL
614 #if defined(HAS_ADL_EDID_PARSER)
616 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address via ADL", __FUNCTION__
);
618 iPA
= adl
.GetPhysicalAddress();
619 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - ADL returned physical address %04x", __FUNCTION__
, iPA
);
623 // try to get the PA from the nvidia driver
624 #if defined(HAS_NVIDIA_EDID_PARSER)
627 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address via nvidia driver", __FUNCTION__
);
629 iPA
= nv
.GetPhysicalAddress();
630 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - nvidia driver returned physical address %04x", __FUNCTION__
, iPA
);
634 // try to get the PA from the OS
637 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address from the OS", __FUNCTION__
);
638 iPA
= CEDIDParser::GetPhysicalAddress();
639 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - OS returned physical address %04x", __FUNCTION__
, iPA
);
645 void *CAdapterPingThread::Process(void)
649 if (m_timeout
.TimeLeft() == 0)
651 /* reinit the timeout */
652 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
654 /* send a ping to the adapter */
656 int iFailedCounter(0);
657 while (!bPinged
&& iFailedCounter
< 3)
659 if (!m_com
->PingAdapter())
661 /* sleep and retry */
662 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
671 if (iFailedCounter
== 3)
673 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
674 m_com
->LIB_CEC
->AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
675 m_com
->StopThread(false);
685 void CAdapterEepromWriteThread::Stop(void)
689 CLockObject
lock(m_mutex
);
690 if (m_iScheduleEepromWrite
> 0)
691 m_com
->LIB_CEC
->AddLog(CEC_LOG_WARNING
, "write thread stopped while a write was queued");
692 m_condition
.Signal();
697 void *CAdapterEepromWriteThread::Process(void)
701 CLockObject
lock(m_mutex
);
702 if ((m_iScheduleEepromWrite
> 0 && m_iScheduleEepromWrite
< GetTimeMs()) ||
703 m_condition
.Wait(m_mutex
, m_bWrite
, 100))
706 if (m_com
->m_commands
->WriteEEPROM())
708 m_iLastEepromWrite
= GetTimeMs();
709 m_iScheduleEepromWrite
= 0;
713 m_iScheduleEepromWrite
= GetTimeMs() + CEC_ADAPTER_EEPROM_WRITE_RETRY
;
720 bool CAdapterEepromWriteThread::Write(void)
722 CLockObject
lock(m_mutex
);
723 if (m_iScheduleEepromWrite
== 0)
725 int64_t iNow
= GetTimeMs();
726 if (m_iLastEepromWrite
+ CEC_ADAPTER_EEPROM_WRITE_INTERVAL
> iNow
)
728 m_com
->LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "delaying eeprom write by %ld ms", m_iLastEepromWrite
+ CEC_ADAPTER_EEPROM_WRITE_INTERVAL
- iNow
);
729 m_iScheduleEepromWrite
= m_iLastEepromWrite
+ CEC_ADAPTER_EEPROM_WRITE_INTERVAL
;
734 m_condition
.Signal();