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 if (m_eepromWriteThread
)
226 m_eepromWriteThread
->Stop();
227 DELETE_AND_NULL(m_eepromWriteThread
);
229 /* stop and delete the ping thread */
230 DELETE_AND_NULL(m_pingThread
);
232 /* close and delete the com port connection */
237 cec_adapter_message_state
CUSBCECAdapterCommunication::Write(const cec_command
&data
, bool &bRetry
, uint8_t iLineTimeout
, bool UNUSED(bIsReply
))
239 cec_adapter_message_state
retVal(ADAPTER_MESSAGE_STATE_UNKNOWN
);
243 CCECAdapterMessage
*output
= new CCECAdapterMessage(data
, iLineTimeout
);
245 /* mark as waiting for an ack from the destination */
246 MarkAsWaiting(data
.destination
);
248 /* send the message */
249 bRetry
= (!m_adapterMessageQueue
->Write(output
) || output
->NeedsRetry()) && output
->transmit_timeout
> 0;
251 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
252 retVal
= output
->state
;
258 void *CUSBCECAdapterCommunication::Process(void)
260 CCECAdapterMessage msg
;
261 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread started");
265 /* read from the serial port */
266 if (!ReadFromDevice(50, 5))
268 libcec_parameter param
;
269 param
.paramData
= NULL
; param
.paramType
= CEC_PARAMETER_TYPE_UNKOWN
;
270 LIB_CEC
->Alert(CEC_ALERT_CONNECTION_LOST
, param
);
275 /* TODO sleep 5 ms so other threads can get a lock */
279 m_adapterMessageQueue
->Clear();
280 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "communication thread ended");
284 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage
&msg
)
286 bool bIsError(msg
.IsError());
287 cec_adapter_messagecode
messageCode(msg
.Message());
288 CLockObject
lock(m_mutex
);
290 if (messageCode
== MSGCODE_FRAME_START
&& msg
.IsACK())
292 m_lastPollDestination
= msg
.Destination();
293 if (msg
.Destination() < CECDEVICE_BROADCAST
)
295 if (!m_bWaitingForAck
[msg
.Destination()] && !msg
.IsEOM())
298 m_callback
->HandlePoll(msg
.Initiator(), msg
.Destination());
301 m_bWaitingForAck
[msg
.Destination()] = false;
304 else if (messageCode
== MSGCODE_RECEIVE_FAILED
)
306 /* hack to suppress warnings when an LG is polling */
307 if (m_lastPollDestination
!= CECDEVICE_UNKNOWN
)
308 bIsError
= m_callback
->HandleReceiveFailed(m_lastPollDestination
);
314 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest
)
316 /* mark as waiting for an ack from the destination */
317 if (dest
< CECDEVICE_BROADCAST
)
319 CLockObject
lock(m_mutex
);
320 m_bWaitingForAck
[dest
] = true;
324 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout
/* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
326 CTimeout
timeout(iTimeout
);
328 ssize_t
iBytesRead(0);
329 bool bGotMsgEnd(true);
331 while (timeout
.TimeLeft() > 0 && ((iBytesRead
= m_port
->Read(buff
, 1024, 5)) > 0 || !bGotMsgEnd
))
334 /* if something was received, wait for MSGEND */
335 for (ssize_t iPtr
= 0; iPtr
< iBytesRead
; iPtr
++)
336 bGotMsgEnd
= buff
[iPtr
] == MSGEND
;
340 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout
)
343 bool bChanged(false);
345 /* only send the command if the timeout changed */
347 CLockObject
lock(m_mutex
);
348 bChanged
= (m_iLineTimeout
!= iTimeout
);
349 m_iLineTimeout
= iTimeout
;
353 bReturn
= m_commands
->SetLineTimeout(iTimeout
);
358 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage
*message
)
360 CLockObject
adapterLock(m_mutex
);
363 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());
364 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
368 /* write the message */
369 if (m_port
->Write(message
->packet
.data
, message
->Size()) != (ssize_t
) message
->Size())
371 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());
372 message
->state
= ADAPTER_MESSAGE_STATE_ERROR
;
373 // this will trigger an alert in the reader thread
378 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "command '%s' sent", message
->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message
->Message()));
379 message
->state
= ADAPTER_MESSAGE_STATE_SENT
;
383 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout
, size_t iSize
/* = 256 */)
385 ssize_t
iBytesRead(0);
390 /* read from the serial port */
392 CLockObject
lock(m_mutex
);
396 iBytesRead
= m_port
->Read(buff
, sizeof(uint8_t) * iSize
, iTimeout
);
398 if (m_port
->GetErrorNumber())
400 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "error reading from serial port: %s", m_port
->GetError().c_str());
406 if (iBytesRead
< 0 || iBytesRead
> 256)
408 else if (iBytesRead
> 0)
410 /* add the data to the current frame */
411 m_adapterMessageQueue
->AddData(buff
, iBytesRead
);
417 CCECAdapterMessage
*CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode
, CCECAdapterMessage
¶ms
, bool bIsRetry
/* = false */)
419 if (!IsOpen() || !m_adapterMessageQueue
)
422 /* create the adapter message for this command */
423 CCECAdapterMessage
*output
= new CCECAdapterMessage
;
424 output
->PushBack(MSGSTART
);
425 output
->PushEscaped((uint8_t)msgCode
);
426 output
->Append(params
);
427 output
->PushBack(MSGEND
);
429 /* write the command */
430 if (!m_adapterMessageQueue
->Write(output
))
432 // this will trigger an alert in the reader thread
433 if (output
->state
== ADAPTER_MESSAGE_STATE_ERROR
)
439 if (!bIsRetry
&& output
->Reply() == MSGCODE_COMMAND_REJECTED
&& msgCode
!= MSGCODE_SET_CONTROLLED
&&
440 msgCode
!= MSGCODE_GET_BUILDDATE
/* same messagecode value had a different meaning in older fw builds */)
442 /* if the controller reported that the command was rejected, and we didn't send the command
443 to set controlled mode, then the controller probably switched to auto mode. set controlled
445 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "setting controlled mode and retrying");
447 if (SetControlledMode(true))
448 return SendCommand(msgCode
, params
, true);
455 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs
/* = CEC_DEFAULT_CONNECT_TIMEOUT */)
458 CTimeout
timeout(iTimeoutMs
> 0 ? iTimeoutMs
: CEC_DEFAULT_TRANSMIT_WAIT
);
460 /* try to ping the adapter */
462 unsigned iPingTry(0);
463 while (timeout
.TimeLeft() > 0 && (bPinged
= PingAdapter()) == false)
465 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry
);
469 /* try to read the firmware version */
470 if (bPinged
&& timeout
.TimeLeft() > 0 && m_commands
->RequestFirmwareVersion() >= 2)
472 /* try to set controlled mode for v2+ firmwares */
473 unsigned iControlledTry(0);
474 bool bControlled(false);
475 while (timeout
.TimeLeft() > 0 && (bControlled
= SetControlledMode(true)) == false)
477 LIB_CEC
->AddLog(CEC_LOG_ERROR
, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry
);
480 bReturn
= bControlled
;
485 /* try to read the build date */
486 m_commands
->RequestBuildDate();
488 SetInitialised(bReturn
);
492 bool CUSBCECAdapterCommunication::IsOpen(void)
494 /* thread is not being stopped, the port is open and the thread is running */
495 return !IsStopped() && m_port
->IsOpen() && IsRunning();
498 std::string
CUSBCECAdapterCommunication::GetError(void) const
500 return m_port
->GetError();
503 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo
/* = true */)
505 CLockObject
lock(m_mutex
);
506 m_bInitialised
= bSetTo
;
509 bool CUSBCECAdapterCommunication::IsInitialised(void)
511 CLockObject
lock(m_mutex
);
512 return m_bInitialised
;
515 bool CUSBCECAdapterCommunication::StartBootloader(void)
517 if (m_port
->IsOpen() && m_commands
->StartBootloader())
525 bool CUSBCECAdapterCommunication::SetLogicalAddresses(const cec_logical_addresses
&addresses
)
528 CLockObject
lock(m_mutex
);
529 if (m_logicalAddresses
== addresses
)
533 if (IsOpen() && m_commands
->SetAckMask(addresses
.AckMask()))
535 CLockObject
lock(m_mutex
);
536 m_logicalAddresses
= addresses
;
540 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "couldn't change the ackmask: the connection is closed");
544 cec_logical_addresses
CUSBCECAdapterCommunication::GetLogicalAddresses(void)
546 cec_logical_addresses addresses
;
547 CLockObject
lock(m_mutex
);
548 addresses
= m_logicalAddresses
;
552 bool CUSBCECAdapterCommunication::PingAdapter(void)
554 return IsOpen() ? m_commands
->PingAdapter() : false;
557 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
559 return m_commands
? m_commands
->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN
;
562 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
564 uint32_t iBuildDate(0);
566 iBuildDate
= m_commands
->GetPersistedBuildDate();
567 if (iBuildDate
== 0 && IsOpen())
568 iBuildDate
= m_commands
->RequestBuildDate();
573 bool CUSBCECAdapterCommunication::ProvidesExtendedResponse(void)
575 uint32_t iBuildDate(0);
577 iBuildDate
= m_commands
->GetPersistedBuildDate();
579 return iBuildDate
>= CEC_FW_DATE_EXTENDED_RESPONSE
;
582 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
584 return GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE
&&
585 GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION
;
588 bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration
&configuration
)
591 m_commands
->PersistConfiguration(configuration
) && m_eepromWriteThread
->Write() :
595 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration
&configuration
)
597 return IsOpen() ? m_commands
->GetConfiguration(configuration
) : false;
600 std::string
CUSBCECAdapterCommunication::GetPortName(void)
602 return m_port
->GetName();
605 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled
)
607 return IsOpen() ? m_commands
->SetControlledMode(controlled
) : false;
610 uint16_t CUSBCECAdapterCommunication::GetPhysicalAddress(void)
614 // try to get the PA from ADL
615 #if defined(HAS_ADL_EDID_PARSER)
617 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address via ADL", __FUNCTION__
);
619 iPA
= adl
.GetPhysicalAddress();
620 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - ADL returned physical address %04x", __FUNCTION__
, iPA
);
624 // try to get the PA from the nvidia driver
625 #if defined(HAS_NVIDIA_EDID_PARSER)
628 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address via nvidia driver", __FUNCTION__
);
630 iPA
= nv
.GetPhysicalAddress();
631 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - nvidia driver returned physical address %04x", __FUNCTION__
, iPA
);
635 // try to get the PA from the OS
638 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - trying to get the physical address from the OS", __FUNCTION__
);
639 iPA
= CEDIDParser::GetPhysicalAddress();
640 LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "%s - OS returned physical address %04x", __FUNCTION__
, iPA
);
646 void *CAdapterPingThread::Process(void)
650 if (m_timeout
.TimeLeft() == 0)
652 /* reinit the timeout */
653 m_timeout
.Init(CEC_ADAPTER_PING_TIMEOUT
);
655 /* send a ping to the adapter */
657 int iFailedCounter(0);
658 while (!bPinged
&& iFailedCounter
< 3)
660 if (!m_com
->PingAdapter())
662 /* sleep and retry */
663 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT
);
672 if (iFailedCounter
== 3)
674 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
675 m_com
->LIB_CEC
->AddLog(CEC_LOG_ERROR
, "failed to ping the adapter 3 times in a row. closing the connection.");
676 m_com
->StopThread(false);
686 void CAdapterEepromWriteThread::Stop(void)
690 CLockObject
lock(m_mutex
);
691 if (m_iScheduleEepromWrite
> 0)
692 m_com
->LIB_CEC
->AddLog(CEC_LOG_WARNING
, "write thread stopped while a write was queued");
693 m_condition
.Signal();
698 void *CAdapterEepromWriteThread::Process(void)
702 CLockObject
lock(m_mutex
);
703 if ((m_iScheduleEepromWrite
> 0 && m_iScheduleEepromWrite
< GetTimeMs()) ||
704 m_condition
.Wait(m_mutex
, m_bWrite
, 100))
707 if (m_com
->m_commands
->WriteEEPROM())
709 m_iLastEepromWrite
= GetTimeMs();
710 m_iScheduleEepromWrite
= 0;
714 m_iScheduleEepromWrite
= GetTimeMs() + CEC_ADAPTER_EEPROM_WRITE_RETRY
;
721 bool CAdapterEepromWriteThread::Write(void)
723 CLockObject
lock(m_mutex
);
724 if (m_iScheduleEepromWrite
== 0)
726 int64_t iNow
= GetTimeMs();
727 if (m_iLastEepromWrite
+ CEC_ADAPTER_EEPROM_WRITE_INTERVAL
> iNow
)
729 m_com
->LIB_CEC
->AddLog(CEC_LOG_DEBUG
, "delaying eeprom write by %ld ms", m_iLastEepromWrite
+ CEC_ADAPTER_EEPROM_WRITE_INTERVAL
- iNow
);
730 m_iScheduleEepromWrite
= m_iLastEepromWrite
+ CEC_ADAPTER_EEPROM_WRITE_INTERVAL
;
735 m_condition
.Signal();