fix OS-X build
[deb_libcec.git] / src / lib / adapter / USBCECAdapterCommunication.cpp
... / ...
CommitLineData
1/*
2 * This file is part of the libCEC(R) library.
3 *
4 * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited. All rights reserved.
5 * libCEC(R) is an original work, containing original code.
6 *
7 * libCEC(R) is a trademark of Pulse-Eight Limited.
8 *
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.
13 *
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.
18 *
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.
22 *
23 *
24 * Alternatively, you can license this library under a commercial license,
25 * please contact Pulse-Eight Licensing for more information.
26 *
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/
31 */
32
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"
44
45using namespace std;
46using namespace CEC;
47using namespace PLATFORM;
48
49#define CEC_ADAPTER_PING_TIMEOUT 15000
50
51// firmware version 2
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
55
56#define LIB_CEC m_callback->GetLib()
57
58CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate /* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
59 IAdapterCommunication(callback),
60 m_port(NULL),
61 m_iLineTimeout(0),
62 m_lastPollDestination(CECDEVICE_UNKNOWN),
63 m_bInitialised(false),
64 m_pingThread(NULL),
65 m_commands(NULL),
66 m_adapterMessageQueue(NULL),
67 m_iAckMask(0xFFFF)
68{
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);
73}
74
75CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
76{
77 Close();
78 DELETE_AND_NULL(m_commands);
79 DELETE_AND_NULL(m_adapterMessageQueue);
80 DELETE_AND_NULL(m_port);
81}
82
83void CUSBCECAdapterCommunication::ResetMessageQueue(void)
84{
85 DELETE_AND_NULL(m_adapterMessageQueue);
86 m_adapterMessageQueue = new CCECAdapterMessageQueue(this);
87 m_adapterMessageQueue->CreateThread();
88}
89
90bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks /* = false */, bool bStartListening /* = true */)
91{
92 bool bConnectionOpened(false);
93 {
94 CLockObject lock(m_mutex);
95
96 /* we need the port settings here */
97 if (!m_port)
98 {
99 LIB_CEC->AddLog(CEC_LOG_ERROR, "port is NULL");
100 return bConnectionOpened;
101 }
102
103 /* return true when the port is already open */
104 if (IsOpen())
105 {
106 LIB_CEC->AddLog(CEC_LOG_WARNING, "port is already open");
107 return true;
108 }
109
110 ResetMessageQueue();
111
112 /* try to open the connection */
113 CStdString strError;
114 CTimeout timeout(iTimeoutMs);
115 while (!bConnectionOpened && timeout.TimeLeft() > 0)
116 {
117 if ((bConnectionOpened = m_port->Open(timeout.TimeLeft())) == false)
118 {
119 strError.Format("error opening serial port '%s': %s", m_port->GetName().c_str(), m_port->GetError().c_str());
120 Sleep(250);
121 }
122 /* and retry every 250ms until the timeout passed */
123 }
124
125 /* return false when we couldn't connect */
126 if (!bConnectionOpened)
127 {
128 LIB_CEC->AddLog(CEC_LOG_ERROR, strError);
129
130 if (m_port->GetErrorNumber() == EACCES)
131 {
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);
136 }
137 else if (m_port->GetErrorNumber() == EBUSY)
138 {
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);
143 }
144 return false;
145 }
146
147 LIB_CEC->AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
148 ClearInputBytes();
149 }
150
151 // always start by setting the ackmask to 0, to clear previous values
152 SetAckMask(0);
153
154 if (!CreateThread())
155 {
156 bConnectionOpened = false;
157 LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a communication thread");
158 }
159 else if (!bSkipChecks && !CheckAdapter())
160 {
161 bConnectionOpened = false;
162 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks");
163 }
164 else if (bStartListening)
165 {
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())
170 {
171 bConnectionOpened = true;
172 }
173 else
174 {
175 bConnectionOpened = false;
176 LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a ping thread");
177 }
178 }
179
180 if (!bConnectionOpened || !bStartListening)
181 StopThread(0);
182
183 return bConnectionOpened;
184}
185
186void CUSBCECAdapterCommunication::Close(void)
187{
188 /* stop the reader thread */
189 StopThread(0);
190
191 CLockObject lock(m_mutex);
192
193 /* set the ackmask to 0 before closing the connection */
194 if (IsOpen() && m_port->GetErrorNumber() == 0)
195 {
196 LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - closing the connection", __FUNCTION__);
197 SetAckMask(0);
198 if (m_commands->GetFirmwareVersion() >= 2)
199 SetControlledMode(false);
200 }
201
202 m_adapterMessageQueue->Clear();
203
204 /* stop and delete the ping thread */
205 DELETE_AND_NULL(m_pingThread);
206
207 /* close and delete the com port connection */
208 if (m_port)
209 m_port->Close();
210}
211
212cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, bool &bRetry, uint8_t iLineTimeout)
213{
214 cec_adapter_message_state retVal(ADAPTER_MESSAGE_STATE_UNKNOWN);
215 if (!IsRunning())
216 return retVal;
217
218 CCECAdapterMessage *output = new CCECAdapterMessage(data, iLineTimeout);
219
220 /* mark as waiting for an ack from the destination */
221 MarkAsWaiting(data.destination);
222
223 /* send the message */
224 bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
225 if (bRetry)
226 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
227 retVal = output->state;
228
229 delete output;
230 return retVal;
231}
232
233void *CUSBCECAdapterCommunication::Process(void)
234{
235 CCECAdapterMessage msg;
236 LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread started");
237
238 while (!IsStopped())
239 {
240 /* read from the serial port */
241 if (!ReadFromDevice(50, 5))
242 {
243 libcec_parameter param;
244 param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN;
245 LIB_CEC->Alert(CEC_ALERT_CONNECTION_LOST, param);
246
247 break;
248 }
249
250 /* TODO sleep 5 ms so other threads can get a lock */
251 Sleep(5);
252 }
253
254 m_adapterMessageQueue->Clear();
255 LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread ended");
256 return NULL;
257}
258
259bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage &msg)
260{
261 bool bIsError(msg.IsError());
262 cec_adapter_messagecode messageCode(msg.Message());
263 CLockObject lock(m_mutex);
264
265 if (messageCode == MSGCODE_FRAME_START && msg.IsACK())
266 {
267 m_lastPollDestination = msg.Destination();
268 if (msg.Destination() < CECDEVICE_BROADCAST)
269 {
270 if (!m_bWaitingForAck[msg.Destination()] && !msg.IsEOM())
271 {
272 if (m_callback)
273 m_callback->HandlePoll(msg.Initiator(), msg.Destination());
274 }
275 else
276 m_bWaitingForAck[msg.Destination()] = false;
277 }
278 }
279 else if (messageCode == MSGCODE_RECEIVE_FAILED)
280 {
281 /* hack to suppress warnings when an LG is polling */
282 if (m_lastPollDestination != CECDEVICE_UNKNOWN)
283 bIsError = m_callback->HandleReceiveFailed(m_lastPollDestination);
284 }
285
286 return bIsError;
287}
288
289void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest)
290{
291 /* mark as waiting for an ack from the destination */
292 if (dest < CECDEVICE_BROADCAST)
293 {
294 CLockObject lock(m_mutex);
295 m_bWaitingForAck[dest] = true;
296 }
297}
298
299void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
300{
301 CTimeout timeout(iTimeout);
302 uint8_t buff[1024];
303 ssize_t iBytesRead(0);
304 bool bGotMsgEnd(true);
305
306 while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd))
307 {
308 bGotMsgEnd = false;
309 /* if something was received, wait for MSGEND */
310 for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++)
311 bGotMsgEnd = buff[iPtr] == MSGEND;
312 }
313}
314
315bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
316{
317 bool bReturn(true);
318 bool bChanged(false);
319
320 /* only send the command if the timeout changed */
321 {
322 CLockObject lock(m_mutex);
323 bChanged = (m_iLineTimeout != iTimeout);
324 m_iLineTimeout = iTimeout;
325 }
326
327 if (bChanged)
328 bReturn = m_commands->SetLineTimeout(iTimeout);
329
330 return bReturn;
331}
332
333bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
334{
335 CLockObject adapterLock(m_mutex);
336 if (!IsOpen())
337 {
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;
340 return false;
341 }
342
343 /* write the message */
344 if (m_port->Write(message->packet.data, message->Size()) != (ssize_t) message->Size())
345 {
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
349 m_port->Close();
350 return false;
351 }
352
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;
355 return true;
356}
357
358bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */)
359{
360 ssize_t iBytesRead(0);
361 uint8_t buff[256];
362 if (iSize > 256)
363 iSize = 256;
364
365 /* read from the serial port */
366 {
367 CLockObject lock(m_mutex);
368 if (!IsOpen())
369 return false;
370
371 iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
372
373 if (m_port->GetErrorNumber())
374 {
375 LIB_CEC->AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
376 m_port->Close();
377 return false;
378 }
379 }
380
381 if (iBytesRead < 0 || iBytesRead > 256)
382 return false;
383 else if (iBytesRead > 0)
384 {
385 /* add the data to the current frame */
386 m_adapterMessageQueue->AddData(buff, iBytesRead);
387 }
388
389 return true;
390}
391
392CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage &params, bool bIsRetry /* = false */)
393{
394 if (!IsOpen() || !m_adapterMessageQueue)
395 return NULL;
396
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);
403
404 /* write the command */
405 if (!m_adapterMessageQueue->Write(output))
406 {
407 // this will trigger an alert in the reader thread
408 if (output->state == ADAPTER_MESSAGE_STATE_ERROR)
409 m_port->Close();
410 return output;
411 }
412 else
413 {
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 */)
416 {
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
419 mode and retry */
420 LIB_CEC->AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying");
421 delete output;
422 if (SetControlledMode(true))
423 return SendCommand(msgCode, params, true);
424 }
425 }
426
427 return output;
428}
429
430bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */)
431{
432 bool bReturn(false);
433 CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT);
434
435 /* try to ping the adapter */
436 bool bPinged(false);
437 unsigned iPingTry(0);
438 while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false)
439 {
440 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
441 CEvent::Sleep(500);
442 }
443
444 /* try to read the firmware version */
445 if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2)
446 {
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)
451 {
452 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry);
453 CEvent::Sleep(500);
454 }
455 bReturn = bControlled;
456 }
457 else
458 bReturn = true;
459
460 /* try to read the build date */
461 m_commands->RequestBuildDate();
462
463 SetInitialised(bReturn);
464 return bReturn;
465}
466
467bool CUSBCECAdapterCommunication::IsOpen(void)
468{
469 /* thread is not being stopped, the port is open and the thread is running */
470 return !IsStopped() && m_port->IsOpen() && IsRunning();
471}
472
473CStdString CUSBCECAdapterCommunication::GetError(void) const
474{
475 return m_port->GetError();
476}
477
478void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */)
479{
480 CLockObject lock(m_mutex);
481 m_bInitialised = bSetTo;
482}
483
484bool CUSBCECAdapterCommunication::IsInitialised(void)
485{
486 CLockObject lock(m_mutex);
487 return m_bInitialised;
488}
489
490bool CUSBCECAdapterCommunication::StartBootloader(void)
491{
492 if (m_port->IsOpen() && m_commands->StartBootloader())
493 {
494 m_port->Close();
495 return true;
496 }
497 return false;
498}
499
500bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
501{
502 {
503 CLockObject lock(m_mutex);
504 if (m_iAckMask == iMask)
505 return true;
506 }
507
508 if (IsOpen() && m_commands->SetAckMask(iMask))
509 {
510 CLockObject lock(m_mutex);
511 m_iAckMask = iMask;
512 return true;
513 }
514
515 LIB_CEC->AddLog(CEC_LOG_DEBUG, "couldn't change the ackmask: the connection is closed");
516 return false;
517}
518
519uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
520{
521 CLockObject lock(m_mutex);
522 return m_iAckMask;
523}
524
525bool CUSBCECAdapterCommunication::PingAdapter(void)
526{
527 return IsOpen() ? m_commands->PingAdapter() : false;
528}
529
530uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
531{
532 return m_commands ? m_commands->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN;
533}
534
535uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
536{
537 return IsOpen() ? m_commands->RequestBuildDate() : m_commands ? m_commands->GetPersistedBuildDate() : 0;
538}
539
540bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
541{
542 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION &&
543 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE;
544}
545
546bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration &configuration)
547{
548 return IsOpen() ? m_commands->PersistConfiguration(configuration) : false;
549}
550
551bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration &configuration)
552{
553 return IsOpen() ? m_commands->GetConfiguration(configuration) : false;
554}
555
556CStdString CUSBCECAdapterCommunication::GetPortName(void)
557{
558 return m_port->GetName();
559}
560
561bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
562{
563 return IsOpen() ? m_commands->SetControlledMode(controlled) : false;
564}
565
566uint16_t CUSBCECAdapterCommunication::GetPhysicalAddress(void)
567{
568 uint16_t iPA(0);
569
570 // try to get the PA from ADL
571#if defined(HAS_ADL_EDID_PARSER)
572 {
573 LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - trying to get the physical address via ADL", __FUNCTION__);
574 CADLEdidParser adl;
575 iPA = adl.GetPhysicalAddress();
576 LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - ADL returned physical address %04x", __FUNCTION__, iPA);
577 }
578#endif
579
580 // try to get the PA from the nvidia driver
581#if defined(HAS_NVIDIA_EDID_PARSER)
582 if (iPA == 0)
583 {
584 LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - trying to get the physical address via nvidia driver", __FUNCTION__);
585 CNVEdidParser nv;
586 iPA = nv.GetPhysicalAddress();
587 LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - nvidia driver returned physical address %04x", __FUNCTION__, iPA);
588 }
589#endif
590
591 // try to get the PA from the OS
592 if (iPA == 0)
593 {
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);
597 }
598
599 return iPA;
600}
601
602void *CAdapterPingThread::Process(void)
603{
604 while (!IsStopped())
605 {
606 if (m_timeout.TimeLeft() == 0)
607 {
608 /* reinit the timeout */
609 m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT);
610
611 /* send a ping to the adapter */
612 bool bPinged(false);
613 int iFailedCounter(0);
614 while (!bPinged && iFailedCounter < 3)
615 {
616 if (!m_com->PingAdapter())
617 {
618 /* sleep and retry */
619 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
620 ++iFailedCounter;
621 }
622 else
623 {
624 bPinged = true;
625 }
626 }
627
628 if (iFailedCounter == 3)
629 {
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);
633 break;
634 }
635 }
636
637 Sleep(500);
638 }
639 return NULL;
640}