cec: hold a lock when changing the ackmask in CUSBCECAdapterCommunication::Open
[deb_libcec.git] / src / lib / adapter / USBCECAdapterCommunication.cpp
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 "../LibCEC.h"
40 #include "../CECProcessor.h"
41
42 using namespace std;
43 using namespace CEC;
44 using namespace PLATFORM;
45
46 #define CEC_ADAPTER_PING_TIMEOUT 15000
47
48 // firmware version 2
49 #define CEC_LATEST_ADAPTER_FW_VERSION 2
50 // firmware date Thu Apr 26 20:14:49 2012 +0000
51 #define CEC_LATEST_ADAPTER_FW_DATE 0x4F99ACB9
52
53 #define LIB_CEC m_callback->GetLib()
54
55 CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate /* = CEC_SERIAL_DEFAULT_BAUDRATE */) :
56 IAdapterCommunication(callback),
57 m_port(NULL),
58 m_iLineTimeout(0),
59 m_lastPollDestination(CECDEVICE_UNKNOWN),
60 m_bInitialised(false),
61 m_pingThread(NULL),
62 m_commands(NULL),
63 m_adapterMessageQueue(NULL),
64 m_iAckMask(0xFFFF)
65 {
66 for (unsigned int iPtr = CECDEVICE_TV; iPtr < CECDEVICE_BROADCAST; iPtr++)
67 m_bWaitingForAck[iPtr] = false;
68 m_port = new CSerialPort(strPort, iBaudRate);
69 m_commands = new CUSBCECAdapterCommands(this);
70 }
71
72 CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void)
73 {
74 Close();
75 DELETE_AND_NULL(m_commands);
76 DELETE_AND_NULL(m_adapterMessageQueue);
77 DELETE_AND_NULL(m_port);
78 }
79
80 void CUSBCECAdapterCommunication::ResetMessageQueue(void)
81 {
82 DELETE_AND_NULL(m_adapterMessageQueue);
83 m_adapterMessageQueue = new CCECAdapterMessageQueue(this);
84 m_adapterMessageQueue->CreateThread();
85 }
86
87 bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks /* = false */, bool bStartListening /* = true */)
88 {
89 bool bConnectionOpened(false);
90 {
91 CLockObject lock(m_mutex);
92
93 /* we need the port settings here */
94 if (!m_port)
95 {
96 LIB_CEC->AddLog(CEC_LOG_ERROR, "port is NULL");
97 return bConnectionOpened;
98 }
99
100 /* return true when the port is already open */
101 if (IsOpen())
102 {
103 LIB_CEC->AddLog(CEC_LOG_WARNING, "port is already open");
104 return true;
105 }
106
107 ResetMessageQueue();
108
109 /* try to open the connection */
110 CStdString strError;
111 CTimeout timeout(iTimeoutMs);
112 while (!bConnectionOpened && timeout.TimeLeft() > 0)
113 {
114 if ((bConnectionOpened = m_port->Open(timeout.TimeLeft())) == false)
115 {
116 strError.Format("error opening serial port '%s': %s", m_port->GetName().c_str(), m_port->GetError().c_str());
117 Sleep(250);
118 }
119 /* and retry every 250ms until the timeout passed */
120 }
121
122 /* return false when we couldn't connect */
123 if (!bConnectionOpened)
124 {
125 LIB_CEC->AddLog(CEC_LOG_ERROR, strError);
126
127 if (m_port->GetErrorNumber() == EACCES)
128 {
129 libcec_parameter param;
130 param.paramType = CEC_PARAMETER_TYPE_STRING;
131 param.paramData = (void*)"No permission to open the device";
132 LIB_CEC->Alert(CEC_ALERT_PERMISSION_ERROR, param);
133 }
134 else if (m_port->GetErrorNumber() == EBUSY)
135 {
136 libcec_parameter param;
137 param.paramType = CEC_PARAMETER_TYPE_STRING;
138 param.paramData = (void*)"The serial port is busy. Only one program can access the device directly.";
139 LIB_CEC->Alert(CEC_ALERT_PORT_BUSY, param);
140 }
141 return false;
142 }
143
144 LIB_CEC->AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting");
145 ClearInputBytes();
146 }
147
148 // always start by setting the ackmask to 0, to clear previous values
149 SetAckMask(0);
150
151 if (!CreateThread())
152 {
153 bConnectionOpened = false;
154 LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a communication thread");
155 }
156 else if (!bSkipChecks && !CheckAdapter())
157 {
158 bConnectionOpened = false;
159 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks");
160 }
161 else if (bStartListening)
162 {
163 /* start a ping thread, that will ping the adapter every 15 seconds
164 if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
165 m_pingThread = new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT);
166 if (m_pingThread->CreateThread())
167 {
168 bConnectionOpened = true;
169 }
170 else
171 {
172 bConnectionOpened = false;
173 LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a ping thread");
174 }
175 }
176
177 if (!bConnectionOpened || !bStartListening)
178 StopThread(0);
179
180 return bConnectionOpened;
181 }
182
183 void CUSBCECAdapterCommunication::Close(void)
184 {
185 /* stop the reader thread */
186 StopThread(0);
187
188 CLockObject lock(m_mutex);
189
190 /* set the ackmask to 0 before closing the connection */
191 if (IsOpen() && m_port->GetErrorNumber() == 0)
192 {
193 LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - closing the connection", __FUNCTION__);
194 SetAckMask(0);
195 if (m_commands->GetFirmwareVersion() >= 2)
196 SetControlledMode(false);
197 }
198
199 m_adapterMessageQueue->Clear();
200
201 /* stop and delete the ping thread */
202 DELETE_AND_NULL(m_pingThread);
203
204 /* close and delete the com port connection */
205 if (m_port)
206 m_port->Close();
207 }
208
209 cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, bool &bRetry, uint8_t iLineTimeout)
210 {
211 cec_adapter_message_state retVal(ADAPTER_MESSAGE_STATE_UNKNOWN);
212 if (!IsRunning())
213 return retVal;
214
215 CCECAdapterMessage *output = new CCECAdapterMessage(data, iLineTimeout);
216
217 /* mark as waiting for an ack from the destination */
218 MarkAsWaiting(data.destination);
219
220 /* send the message */
221 bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
222 if (bRetry)
223 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
224 retVal = output->state;
225
226 delete output;
227 return retVal;
228 }
229
230 void *CUSBCECAdapterCommunication::Process(void)
231 {
232 CCECAdapterMessage msg;
233 LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread started");
234
235 while (!IsStopped())
236 {
237 /* read from the serial port */
238 if (!ReadFromDevice(50, 5))
239 {
240 libcec_parameter param;
241 param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN;
242 LIB_CEC->Alert(CEC_ALERT_CONNECTION_LOST, param);
243
244 break;
245 }
246
247 /* TODO sleep 5 ms so other threads can get a lock */
248 Sleep(5);
249 }
250
251 m_adapterMessageQueue->Clear();
252 LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread ended");
253 return NULL;
254 }
255
256 bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage &msg)
257 {
258 bool bIsError(msg.IsError());
259 cec_adapter_messagecode messageCode(msg.Message());
260 CLockObject lock(m_mutex);
261
262 if (messageCode == MSGCODE_FRAME_START && msg.IsACK())
263 {
264 m_lastPollDestination = msg.Destination();
265 if (msg.Destination() < CECDEVICE_BROADCAST)
266 {
267 if (!m_bWaitingForAck[msg.Destination()] && !msg.IsEOM())
268 {
269 if (m_callback)
270 m_callback->HandlePoll(msg.Initiator(), msg.Destination());
271 }
272 else
273 m_bWaitingForAck[msg.Destination()] = false;
274 }
275 }
276 else if (messageCode == MSGCODE_RECEIVE_FAILED)
277 {
278 /* hack to suppress warnings when an LG is polling */
279 if (m_lastPollDestination != CECDEVICE_UNKNOWN)
280 bIsError = m_callback->HandleReceiveFailed(m_lastPollDestination);
281 }
282
283 return bIsError;
284 }
285
286 void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest)
287 {
288 /* mark as waiting for an ack from the destination */
289 if (dest < CECDEVICE_BROADCAST)
290 {
291 CLockObject lock(m_mutex);
292 m_bWaitingForAck[dest] = true;
293 }
294 }
295
296 void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = CEC_CLEAR_INPUT_DEFAULT_WAIT */)
297 {
298 CTimeout timeout(iTimeout);
299 uint8_t buff[1024];
300 ssize_t iBytesRead(0);
301 bool bGotMsgEnd(true);
302
303 while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd))
304 {
305 bGotMsgEnd = false;
306 /* if something was received, wait for MSGEND */
307 for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++)
308 bGotMsgEnd = buff[iPtr] == MSGEND;
309 }
310 }
311
312 bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout)
313 {
314 bool bReturn(true);
315 bool bChanged(false);
316
317 /* only send the command if the timeout changed */
318 {
319 CLockObject lock(m_mutex);
320 bChanged = (m_iLineTimeout != iTimeout);
321 m_iLineTimeout = iTimeout;
322 }
323
324 if (bChanged)
325 bReturn = m_commands->SetLineTimeout(iTimeout);
326
327 return bReturn;
328 }
329
330 bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
331 {
332 CLockObject adapterLock(m_mutex);
333 if (!IsOpen())
334 {
335 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());
336 message->state = ADAPTER_MESSAGE_STATE_ERROR;
337 return false;
338 }
339
340 /* write the message */
341 if (m_port->Write(message->packet.data, message->Size()) != (ssize_t) message->Size())
342 {
343 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());
344 message->state = ADAPTER_MESSAGE_STATE_ERROR;
345 // this will trigger an alert in the reader thread
346 m_port->Close();
347 return false;
348 }
349
350 LIB_CEC->AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message()));
351 message->state = ADAPTER_MESSAGE_STATE_SENT;
352 return true;
353 }
354
355 bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */)
356 {
357 ssize_t iBytesRead(0);
358 uint8_t buff[256];
359 if (iSize > 256)
360 iSize = 256;
361
362 /* read from the serial port */
363 {
364 CLockObject lock(m_mutex);
365 if (!IsOpen())
366 return false;
367
368 iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
369
370 if (m_port->GetErrorNumber())
371 {
372 LIB_CEC->AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
373 m_port->Close();
374 return false;
375 }
376 }
377
378 if (iBytesRead < 0 || iBytesRead > 256)
379 return false;
380 else if (iBytesRead > 0)
381 {
382 /* add the data to the current frame */
383 m_adapterMessageQueue->AddData(buff, iBytesRead);
384 }
385
386 return true;
387 }
388
389 CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage &params, bool bIsRetry /* = false */)
390 {
391 if (!IsOpen() || !m_adapterMessageQueue)
392 return NULL;
393
394 /* create the adapter message for this command */
395 CCECAdapterMessage *output = new CCECAdapterMessage;
396 output->PushBack(MSGSTART);
397 output->PushEscaped((uint8_t)msgCode);
398 output->Append(params);
399 output->PushBack(MSGEND);
400
401 /* write the command */
402 if (!m_adapterMessageQueue->Write(output))
403 {
404 // this will trigger an alert in the reader thread
405 if (output->state == ADAPTER_MESSAGE_STATE_ERROR)
406 m_port->Close();
407 return output;
408 }
409 else
410 {
411 if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED &&
412 msgCode != MSGCODE_GET_BUILDDATE /* same messagecode value had a different meaning in older fw builds */)
413 {
414 /* if the controller reported that the command was rejected, and we didn't send the command
415 to set controlled mode, then the controller probably switched to auto mode. set controlled
416 mode and retry */
417 LIB_CEC->AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying");
418 delete output;
419 if (SetControlledMode(true))
420 return SendCommand(msgCode, params, true);
421 }
422 }
423
424 return output;
425 }
426
427 bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */)
428 {
429 bool bReturn(false);
430 CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT);
431
432 /* try to ping the adapter */
433 bool bPinged(false);
434 unsigned iPingTry(0);
435 while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false)
436 {
437 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry);
438 CEvent::Sleep(500);
439 }
440
441 /* try to read the firmware version */
442 if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2)
443 {
444 /* try to set controlled mode for v2+ firmwares */
445 unsigned iControlledTry(0);
446 bool bControlled(false);
447 while (timeout.TimeLeft() > 0 && (bControlled = SetControlledMode(true)) == false)
448 {
449 LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry);
450 CEvent::Sleep(500);
451 }
452 bReturn = bControlled;
453 }
454 else
455 bReturn = true;
456
457 /* try to read the build date */
458 m_commands->RequestBuildDate();
459
460 SetInitialised(bReturn);
461 return bReturn;
462 }
463
464 bool CUSBCECAdapterCommunication::IsOpen(void)
465 {
466 /* thread is not being stopped, the port is open and the thread is running */
467 return !IsStopped() && m_port->IsOpen() && IsRunning();
468 }
469
470 CStdString CUSBCECAdapterCommunication::GetError(void) const
471 {
472 return m_port->GetError();
473 }
474
475 void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */)
476 {
477 CLockObject lock(m_mutex);
478 m_bInitialised = bSetTo;
479 }
480
481 bool CUSBCECAdapterCommunication::IsInitialised(void)
482 {
483 CLockObject lock(m_mutex);
484 return m_bInitialised;
485 }
486
487 bool CUSBCECAdapterCommunication::StartBootloader(void)
488 {
489 if (m_port->IsOpen() && m_commands->StartBootloader())
490 {
491 m_port->Close();
492 return true;
493 }
494 return false;
495 }
496
497 bool CUSBCECAdapterCommunication::SetAckMask(uint16_t iMask)
498 {
499 {
500 CLockObject lock(m_mutex);
501 if (m_iAckMask == iMask)
502 return true;
503 }
504
505 if (IsOpen() && m_commands->SetAckMask(iMask))
506 {
507 CLockObject lock(m_mutex);
508 m_iAckMask = iMask;
509 return true;
510 }
511
512 LIB_CEC->AddLog(CEC_LOG_ERROR, "couldn't change the ackmask: the connection is closed");
513 return false;
514 }
515
516 uint16_t CUSBCECAdapterCommunication::GetAckMask(void)
517 {
518 CLockObject lock(m_mutex);
519 return m_iAckMask;
520 }
521
522 bool CUSBCECAdapterCommunication::PingAdapter(void)
523 {
524 return IsOpen() ? m_commands->PingAdapter() : false;
525 }
526
527 uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
528 {
529 return IsOpen() ? m_commands->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN;
530 }
531
532 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
533 {
534 return IsOpen() ? m_commands->RequestBuildDate() : 0;
535 }
536
537 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
538 {
539 return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION &&
540 GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE;
541 }
542
543 bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration &configuration)
544 {
545 return IsOpen() ? m_commands->PersistConfiguration(configuration) : false;
546 }
547
548 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration &configuration)
549 {
550 return IsOpen() ? m_commands->GetConfiguration(configuration) : false;
551 }
552
553 CStdString CUSBCECAdapterCommunication::GetPortName(void)
554 {
555 return m_port->GetName();
556 }
557
558 bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled)
559 {
560 return IsOpen() ? m_commands->SetControlledMode(controlled) : false;
561 }
562
563 void *CAdapterPingThread::Process(void)
564 {
565 while (!IsStopped())
566 {
567 if (m_timeout.TimeLeft() == 0)
568 {
569 /* reinit the timeout */
570 m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT);
571
572 /* send a ping to the adapter */
573 bool bPinged(false);
574 int iFailedCounter(0);
575 while (!bPinged && iFailedCounter < 3)
576 {
577 if (!m_com->PingAdapter())
578 {
579 /* sleep and retry */
580 Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
581 ++iFailedCounter;
582 }
583 else
584 {
585 bPinged = true;
586 }
587 }
588
589 if (iFailedCounter == 3)
590 {
591 /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
592 m_com->LIB_CEC->AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection.");
593 m_com->StopThread(false);
594 break;
595 }
596 }
597
598 Sleep(500);
599 }
600 return NULL;
601 }