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