Commit | Line | Data |
---|---|---|
cbbe90dd JB |
1 | /* |
2 | * This file is part of the libCEC(R) library. | |
3 | * | |
4 | * libCEC(R) is Copyright (C) 2011-2013 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 "env.h" | |
34 | #include "USBCECAdapterCommunication.h" | |
35 | ||
36 | #include "USBCECAdapterCommands.h" | |
37 | #include "USBCECAdapterMessageQueue.h" | |
38 | #include "USBCECAdapterMessage.h" | |
39 | #include "USBCECAdapterDetection.h" | |
40 | #include "lib/platform/sockets/serialport.h" | |
41 | #include "lib/platform/util/timeutils.h" | |
42 | #include "lib/platform/util/util.h" | |
43 | #include "lib/platform/util/edid.h" | |
44 | #include "lib/platform/adl/adl-edid.h" | |
45 | #include "lib/platform/nvidia/nv-edid.h" | |
46 | #include "lib/LibCEC.h" | |
47 | #include "lib/CECProcessor.h" | |
48 | ||
49 | using namespace std; | |
50 | using namespace CEC; | |
51 | using namespace PLATFORM; | |
52 | ||
53 | #define CEC_ADAPTER_PING_TIMEOUT 15000 | |
54 | #define CEC_ADAPTER_EEPROM_WRITE_INTERVAL 30000 | |
55 | #define CEC_ADAPTER_EEPROM_WRITE_RETRY 5000 | |
56 | ||
57 | // firmware version 3 | |
58 | #define CEC_LATEST_ADAPTER_FW_VERSION 3 | |
59 | // firmware date Thu Nov 15 11:09:45 2012 | |
60 | #define CEC_LATEST_ADAPTER_FW_DATE 0x50a4cd79 | |
61 | ||
62 | #define CEC_FW_DATE_EXTENDED_RESPONSE 0x501a4b0c | |
63 | #define CEC_FW_DATE_DESCRIPTOR2 0x5045dbf5 | |
64 | ||
65 | #define LIB_CEC m_callback->GetLib() | |
66 | ||
67 | CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCallback *callback, const char *strPort, uint16_t iBaudRate /* = CEC_SERIAL_DEFAULT_BAUDRATE */) : | |
68 | IAdapterCommunication(callback), | |
69 | m_port(NULL), | |
70 | m_iLineTimeout(0), | |
71 | m_lastPollDestination(CECDEVICE_UNKNOWN), | |
72 | m_bInitialised(false), | |
73 | m_pingThread(NULL), | |
74 | m_eepromWriteThread(NULL), | |
75 | m_commands(NULL), | |
76 | m_adapterMessageQueue(NULL) | |
77 | { | |
78 | m_logicalAddresses.Clear(); | |
79 | for (unsigned int iPtr = CECDEVICE_TV; iPtr < CECDEVICE_BROADCAST; iPtr++) | |
80 | m_bWaitingForAck[iPtr] = false; | |
81 | m_port = new CSerialPort(strPort, iBaudRate); | |
82 | m_commands = new CUSBCECAdapterCommands(this); | |
83 | } | |
84 | ||
85 | CUSBCECAdapterCommunication::~CUSBCECAdapterCommunication(void) | |
86 | { | |
87 | Close(); | |
88 | DELETE_AND_NULL(m_commands); | |
89 | DELETE_AND_NULL(m_adapterMessageQueue); | |
90 | DELETE_AND_NULL(m_port); | |
91 | } | |
92 | ||
93 | void CUSBCECAdapterCommunication::ResetMessageQueue(void) | |
94 | { | |
95 | DELETE_AND_NULL(m_adapterMessageQueue); | |
96 | m_adapterMessageQueue = new CCECAdapterMessageQueue(this); | |
97 | m_adapterMessageQueue->CreateThread(); | |
98 | } | |
99 | ||
100 | bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */, bool bSkipChecks /* = false */, bool bStartListening /* = true */) | |
101 | { | |
102 | bool bConnectionOpened(false); | |
103 | { | |
104 | CLockObject lock(m_mutex); | |
105 | ||
106 | /* we need the port settings here */ | |
107 | if (!m_port) | |
108 | { | |
109 | LIB_CEC->AddLog(CEC_LOG_ERROR, "port is NULL"); | |
110 | return bConnectionOpened; | |
111 | } | |
112 | ||
113 | /* return true when the port is already open */ | |
114 | if (IsOpen()) | |
115 | { | |
116 | LIB_CEC->AddLog(CEC_LOG_WARNING, "port is already open"); | |
117 | return true; | |
118 | } | |
119 | ||
120 | ResetMessageQueue(); | |
121 | ||
122 | /* try to open the connection */ | |
123 | CStdString strError; | |
124 | CTimeout timeout(iTimeoutMs); | |
125 | while (!bConnectionOpened && timeout.TimeLeft() > 0) | |
126 | { | |
127 | if ((bConnectionOpened = m_port->Open(timeout.TimeLeft())) == false) | |
128 | { | |
129 | strError.Format("error opening serial port '%s': %s", m_port->GetName().c_str(), m_port->GetError().c_str()); | |
130 | Sleep(250); | |
131 | } | |
132 | /* and retry every 250ms until the timeout passed */ | |
133 | } | |
134 | ||
135 | /* return false when we couldn't connect */ | |
136 | if (!bConnectionOpened) | |
137 | { | |
138 | LIB_CEC->AddLog(CEC_LOG_ERROR, strError); | |
139 | ||
140 | if (m_port->GetErrorNumber() == EACCES) | |
141 | { | |
142 | libcec_parameter param; | |
143 | param.paramType = CEC_PARAMETER_TYPE_STRING; | |
144 | param.paramData = (void*)"No permission to open the device"; | |
145 | LIB_CEC->Alert(CEC_ALERT_PERMISSION_ERROR, param); | |
146 | } | |
147 | else if (m_port->GetErrorNumber() == EBUSY) | |
148 | { | |
149 | libcec_parameter param; | |
150 | param.paramType = CEC_PARAMETER_TYPE_STRING; | |
151 | param.paramData = (void*)"The serial port is busy. Only one program can access the device directly."; | |
152 | LIB_CEC->Alert(CEC_ALERT_PORT_BUSY, param); | |
153 | } | |
154 | return false; | |
155 | } | |
156 | ||
157 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "connection opened, clearing any previous input and waiting for active transmissions to end before starting"); | |
158 | ClearInputBytes(); | |
159 | } | |
160 | ||
161 | // always start by setting the ackmask to 0, to clear previous values | |
162 | cec_logical_addresses addresses; addresses.Clear(); | |
163 | SetLogicalAddresses(addresses); | |
164 | ||
165 | if (!CreateThread()) | |
166 | { | |
167 | bConnectionOpened = false; | |
168 | LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a communication thread"); | |
169 | } | |
170 | else if (!bSkipChecks && !CheckAdapter()) | |
171 | { | |
172 | bConnectionOpened = false; | |
173 | LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter failed to pass basic checks"); | |
174 | } | |
175 | else if (bStartListening) | |
176 | { | |
177 | /* start the eeprom write thread, that handles all eeprom writes async */ | |
178 | m_eepromWriteThread = new CAdapterEepromWriteThread(this); | |
179 | if (!m_eepromWriteThread->CreateThread()) | |
180 | { | |
181 | bConnectionOpened = false; | |
182 | LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create the eeprom write thread"); | |
183 | } | |
184 | else | |
185 | { | |
186 | /* start a ping thread, that will ping the adapter every 15 seconds | |
187 | if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */ | |
188 | m_pingThread = new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT); | |
189 | if (m_pingThread->CreateThread()) | |
190 | { | |
191 | bConnectionOpened = true; | |
192 | } | |
193 | else | |
194 | { | |
195 | bConnectionOpened = false; | |
196 | LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a ping thread"); | |
197 | } | |
198 | } | |
199 | } | |
200 | ||
201 | if (!bConnectionOpened || !bStartListening) | |
202 | StopThread(0); | |
203 | ||
204 | return bConnectionOpened; | |
205 | } | |
206 | ||
207 | void CUSBCECAdapterCommunication::Close(void) | |
208 | { | |
209 | /* stop the reader thread */ | |
210 | StopThread(0); | |
211 | ||
212 | CLockObject lock(m_mutex); | |
213 | ||
214 | /* set the ackmask to 0 before closing the connection */ | |
215 | if (IsOpen() && m_port->GetErrorNumber() == 0) | |
216 | { | |
217 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - closing the connection", __FUNCTION__); | |
218 | cec_logical_addresses addresses; addresses.Clear(); | |
219 | SetLogicalAddresses(addresses); | |
220 | if (m_commands->GetFirmwareVersion() >= 2) | |
221 | SetControlledMode(false); | |
222 | } | |
223 | ||
224 | m_adapterMessageQueue->Clear(); | |
225 | ||
226 | /* stop and delete the write thread */ | |
227 | if (m_eepromWriteThread) | |
228 | m_eepromWriteThread->Stop(); | |
229 | DELETE_AND_NULL(m_eepromWriteThread); | |
230 | ||
231 | /* stop and delete the ping thread */ | |
232 | DELETE_AND_NULL(m_pingThread); | |
233 | ||
234 | /* close and delete the com port connection */ | |
235 | if (m_port) | |
236 | m_port->Close(); | |
237 | } | |
238 | ||
239 | cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, bool &bRetry, uint8_t iLineTimeout, bool bIsReply) | |
240 | { | |
241 | cec_adapter_message_state retVal(ADAPTER_MESSAGE_STATE_UNKNOWN); | |
242 | if (!IsRunning()) | |
243 | return retVal; | |
244 | ||
245 | CCECAdapterMessage *output = new CCECAdapterMessage(data, iLineTimeout); | |
246 | output->bFireAndForget = bIsReply; | |
247 | ||
248 | /* mark as waiting for an ack from the destination */ | |
249 | MarkAsWaiting(data.destination); | |
250 | ||
251 | /* send the message */ | |
252 | if (bIsReply) | |
253 | { | |
254 | retVal = m_adapterMessageQueue->Write(output) ? | |
255 | ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT : ADAPTER_MESSAGE_STATE_ERROR; | |
256 | } | |
257 | else | |
258 | { | |
259 | bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0; | |
260 | if (bRetry) | |
261 | Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT); | |
262 | retVal = output->state; | |
263 | ||
264 | delete output; | |
265 | } | |
266 | return retVal; | |
267 | } | |
268 | ||
269 | void *CUSBCECAdapterCommunication::Process(void) | |
270 | { | |
271 | CCECAdapterMessage msg; | |
272 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread started"); | |
273 | ||
274 | while (!IsStopped()) | |
275 | { | |
276 | /* read from the serial port */ | |
277 | if (!ReadFromDevice(50, 5)) | |
278 | { | |
279 | libcec_parameter param; | |
280 | param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN; | |
281 | LIB_CEC->Alert(CEC_ALERT_CONNECTION_LOST, param); | |
282 | ||
283 | break; | |
284 | } | |
285 | ||
286 | /* TODO sleep 5 ms so other threads can get a lock */ | |
287 | if (!IsStopped()) | |
288 | Sleep(5); | |
289 | } | |
290 | ||
291 | m_adapterMessageQueue->Clear(); | |
292 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread ended"); | |
293 | return NULL; | |
294 | } | |
295 | ||
296 | bool CUSBCECAdapterCommunication::HandlePoll(const CCECAdapterMessage &msg) | |
297 | { | |
298 | bool bIsError(msg.IsError()); | |
299 | cec_adapter_messagecode messageCode(msg.Message()); | |
300 | CLockObject lock(m_mutex); | |
301 | ||
302 | if (messageCode == MSGCODE_FRAME_START && msg.IsACK()) | |
303 | { | |
304 | m_lastPollDestination = msg.Destination(); | |
305 | if (msg.Destination() < CECDEVICE_BROADCAST) | |
306 | { | |
307 | CLockObject waitingLock(m_waitingMutex); | |
308 | if (!m_bWaitingForAck[msg.Destination()] && !msg.IsEOM()) | |
309 | { | |
310 | if (m_callback) | |
311 | m_callback->HandlePoll(msg.Initiator(), msg.Destination()); | |
312 | } | |
313 | else | |
314 | m_bWaitingForAck[msg.Destination()] = false; | |
315 | } | |
316 | } | |
317 | else if (messageCode == MSGCODE_RECEIVE_FAILED) | |
318 | { | |
319 | /* hack to suppress warnings when an LG is polling */ | |
320 | if (m_lastPollDestination != CECDEVICE_UNKNOWN) | |
321 | bIsError = m_callback->HandleReceiveFailed(m_lastPollDestination); | |
322 | } | |
323 | ||
324 | return bIsError; | |
325 | } | |
326 | ||
327 | void CUSBCECAdapterCommunication::MarkAsWaiting(const cec_logical_address dest) | |
328 | { | |
329 | /* mark as waiting for an ack from the destination */ | |
330 | if (dest < CECDEVICE_BROADCAST) | |
331 | { | |
332 | CLockObject waitingLock(m_waitingMutex); | |
333 | m_bWaitingForAck[dest] = true; | |
334 | } | |
335 | } | |
336 | ||
337 | void CUSBCECAdapterCommunication::ClearInputBytes(uint32_t iTimeout /* = CEC_CLEAR_INPUT_DEFAULT_WAIT */) | |
338 | { | |
339 | CTimeout timeout(iTimeout); | |
340 | uint8_t buff[1024]; | |
341 | ssize_t iBytesRead(0); | |
342 | bool bGotMsgEnd(true); | |
343 | ||
344 | while (timeout.TimeLeft() > 0 && ((iBytesRead = m_port->Read(buff, 1024, 5)) > 0 || !bGotMsgEnd)) | |
345 | { | |
346 | bGotMsgEnd = false; | |
347 | /* if something was received, wait for MSGEND */ | |
348 | for (ssize_t iPtr = 0; iPtr < iBytesRead; iPtr++) | |
349 | bGotMsgEnd = buff[iPtr] == MSGEND; | |
350 | } | |
351 | } | |
352 | ||
353 | bool CUSBCECAdapterCommunication::SetLineTimeout(uint8_t iTimeout) | |
354 | { | |
355 | bool bReturn(true); | |
356 | bool bChanged(false); | |
357 | ||
358 | /* only send the command if the timeout changed */ | |
359 | { | |
360 | CLockObject lock(m_mutex); | |
361 | bChanged = (m_iLineTimeout != iTimeout); | |
362 | m_iLineTimeout = iTimeout; | |
363 | } | |
364 | ||
365 | if (bChanged) | |
366 | bReturn = m_commands->SetLineTimeout(iTimeout); | |
367 | ||
368 | return bReturn; | |
369 | } | |
370 | ||
371 | bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message) | |
372 | { | |
373 | CLockObject adapterLock(m_mutex); | |
374 | if (!IsOpen()) | |
375 | { | |
376 | 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()); | |
377 | message->state = ADAPTER_MESSAGE_STATE_ERROR; | |
378 | return false; | |
379 | } | |
380 | ||
381 | /* write the message */ | |
382 | if (m_port->Write(message->packet.data, message->Size()) != (ssize_t) message->Size()) | |
383 | { | |
384 | 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()); | |
385 | message->state = ADAPTER_MESSAGE_STATE_ERROR; | |
386 | // let the higher level close the port | |
387 | return false; | |
388 | } | |
389 | ||
390 | #ifdef CEC_DEBUGGING | |
391 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message())); | |
392 | #endif | |
393 | message->state = ADAPTER_MESSAGE_STATE_SENT; | |
394 | return true; | |
395 | } | |
396 | ||
397 | bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize /* = 256 */) | |
398 | { | |
399 | ssize_t iBytesRead(0); | |
400 | uint8_t buff[256]; | |
401 | if (iSize > 256) | |
402 | iSize = 256; | |
403 | ||
404 | /* read from the serial port */ | |
405 | { | |
406 | CLockObject lock(m_mutex); | |
407 | if (!IsOpen()) | |
408 | return false; | |
409 | ||
410 | do { | |
411 | /* retry Read() if it was interrupted */ | |
412 | iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout); | |
413 | } while(m_port->GetErrorNumber() == EINTR); | |
414 | ||
415 | ||
416 | if (m_port->GetErrorNumber()) | |
417 | { | |
418 | LIB_CEC->AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str()); | |
419 | // let the higher level close the port | |
420 | return false; | |
421 | } | |
422 | } | |
423 | ||
424 | if (iBytesRead < 0 || iBytesRead > 256) | |
425 | return false; | |
426 | else if (iBytesRead > 0) | |
427 | { | |
428 | /* add the data to the current frame */ | |
429 | m_adapterMessageQueue->AddData(buff, iBytesRead); | |
430 | } | |
431 | ||
432 | return true; | |
433 | } | |
434 | ||
435 | CCECAdapterMessage *CUSBCECAdapterCommunication::SendCommand(cec_adapter_messagecode msgCode, CCECAdapterMessage ¶ms, bool bIsRetry /* = false */) | |
436 | { | |
437 | if (!IsOpen() || !m_adapterMessageQueue) | |
438 | return NULL; | |
439 | ||
440 | /* create the adapter message for this command */ | |
441 | CCECAdapterMessage *output = new CCECAdapterMessage; | |
442 | output->PushBack(MSGSTART); | |
443 | output->PushEscaped((uint8_t)msgCode); | |
444 | output->Append(params); | |
445 | output->PushBack(MSGEND); | |
446 | ||
447 | /* write the command */ | |
448 | if (!m_adapterMessageQueue->Write(output)) | |
449 | { | |
450 | // this will trigger an alert in the reader thread | |
451 | if (output->state == ADAPTER_MESSAGE_STATE_ERROR) | |
452 | m_port->Close(); | |
453 | return output; | |
454 | } | |
455 | else | |
456 | { | |
457 | if (!bIsRetry && output->Reply() == MSGCODE_COMMAND_REJECTED && msgCode != MSGCODE_SET_CONTROLLED && | |
458 | msgCode != MSGCODE_GET_BUILDDATE /* same messagecode value had a different meaning in older fw builds */) | |
459 | { | |
460 | /* if the controller reported that the command was rejected, and we didn't send the command | |
461 | to set controlled mode, then the controller probably switched to auto mode. set controlled | |
462 | mode and retry */ | |
463 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "setting controlled mode and retrying"); | |
464 | delete output; | |
465 | if (SetControlledMode(true)) | |
466 | return SendCommand(msgCode, params, true); | |
467 | } | |
468 | } | |
469 | ||
470 | return output; | |
471 | } | |
472 | ||
473 | bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONNECT_TIMEOUT */) | |
474 | { | |
475 | bool bReturn(false); | |
476 | CTimeout timeout(iTimeoutMs > 0 ? iTimeoutMs : CEC_DEFAULT_TRANSMIT_WAIT); | |
477 | ||
478 | /* try to ping the adapter */ | |
479 | bool bPinged(false); | |
480 | unsigned iPingTry(0); | |
481 | while (timeout.TimeLeft() > 0 && (bPinged = PingAdapter()) == false) | |
482 | { | |
483 | LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to a ping (try %d)", ++iPingTry); | |
484 | CEvent::Sleep(500); | |
485 | } | |
486 | ||
487 | /* try to read the firmware version */ | |
488 | if (bPinged && timeout.TimeLeft() > 0 && m_commands->RequestFirmwareVersion() >= 2) | |
489 | { | |
490 | /* try to set controlled mode for v2+ firmwares */ | |
491 | unsigned iControlledTry(0); | |
492 | bool bControlled(false); | |
493 | while (timeout.TimeLeft() > 0 && (bControlled = SetControlledMode(true)) == false) | |
494 | { | |
495 | LIB_CEC->AddLog(CEC_LOG_ERROR, "the adapter did not respond correctly to setting controlled mode (try %d)", ++iControlledTry); | |
496 | CEvent::Sleep(500); | |
497 | } | |
498 | bReturn = bControlled; | |
499 | } | |
500 | else | |
501 | bReturn = true; | |
502 | ||
503 | if (m_commands->GetFirmwareVersion() >= 2) | |
504 | { | |
505 | /* try to read the build date */ | |
506 | m_commands->RequestBuildDate(); | |
507 | ||
508 | /* try to read the adapter type */ | |
509 | m_commands->RequestAdapterType(); | |
510 | } | |
511 | ||
512 | SetInitialised(bReturn); | |
513 | return bReturn; | |
514 | } | |
515 | ||
516 | bool CUSBCECAdapterCommunication::IsOpen(void) | |
517 | { | |
518 | /* thread is not being stopped, the port is open and the thread is running */ | |
519 | return !IsStopped() && m_port->IsOpen() && IsRunning(); | |
520 | } | |
521 | ||
522 | std::string CUSBCECAdapterCommunication::GetError(void) const | |
523 | { | |
524 | return m_port->GetError(); | |
525 | } | |
526 | ||
527 | void CUSBCECAdapterCommunication::SetInitialised(bool bSetTo /* = true */) | |
528 | { | |
529 | CLockObject lock(m_mutex); | |
530 | m_bInitialised = bSetTo; | |
531 | } | |
532 | ||
533 | bool CUSBCECAdapterCommunication::IsInitialised(void) | |
534 | { | |
535 | CLockObject lock(m_mutex); | |
536 | return m_bInitialised; | |
537 | } | |
538 | ||
539 | bool CUSBCECAdapterCommunication::StartBootloader(void) | |
540 | { | |
541 | if (m_port->IsOpen() && m_commands->StartBootloader()) | |
542 | { | |
543 | m_port->Close(); | |
544 | return true; | |
545 | } | |
546 | return false; | |
547 | } | |
548 | ||
549 | bool CUSBCECAdapterCommunication::SetLogicalAddresses(const cec_logical_addresses &addresses) | |
550 | { | |
551 | { | |
552 | CLockObject lock(m_mutex); | |
553 | if (m_logicalAddresses == addresses) | |
554 | return true; | |
555 | } | |
556 | ||
557 | if (IsOpen() && m_commands->SetAckMask(addresses.AckMask())) | |
558 | { | |
559 | CLockObject lock(m_mutex); | |
560 | m_logicalAddresses = addresses; | |
561 | return true; | |
562 | } | |
563 | ||
564 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "couldn't change the ackmask: the connection is closed"); | |
565 | return false; | |
566 | } | |
567 | ||
568 | cec_logical_addresses CUSBCECAdapterCommunication::GetLogicalAddresses(void) | |
569 | { | |
570 | cec_logical_addresses addresses; | |
571 | CLockObject lock(m_mutex); | |
572 | addresses = m_logicalAddresses; | |
573 | return addresses; | |
574 | } | |
575 | ||
576 | bool CUSBCECAdapterCommunication::PingAdapter(void) | |
577 | { | |
578 | return IsOpen() ? m_commands->PingAdapter() : false; | |
579 | } | |
580 | ||
581 | uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void) | |
582 | { | |
583 | return m_commands ? m_commands->GetFirmwareVersion() : CEC_FW_VERSION_UNKNOWN; | |
584 | } | |
585 | ||
586 | uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void) | |
587 | { | |
588 | uint32_t iBuildDate(0); | |
589 | if (m_commands) | |
590 | iBuildDate = m_commands->GetPersistedBuildDate(); | |
591 | if (iBuildDate == 0 && IsOpen()) | |
592 | iBuildDate = m_commands->RequestBuildDate(); | |
593 | ||
594 | return iBuildDate; | |
595 | } | |
596 | ||
597 | cec_adapter_type CUSBCECAdapterCommunication::GetAdapterType(void) | |
598 | { | |
599 | cec_adapter_type type(ADAPTERTYPE_UNKNOWN); | |
600 | if (m_commands) | |
601 | type = (cec_adapter_type)m_commands->GetPersistedAdapterType(); | |
602 | if (type == ADAPTERTYPE_UNKNOWN && IsOpen()) | |
603 | type = (cec_adapter_type)m_commands->RequestAdapterType(); | |
604 | ||
605 | return type; | |
606 | } | |
607 | ||
608 | bool CUSBCECAdapterCommunication::ProvidesExtendedResponse(void) | |
609 | { | |
610 | uint32_t iBuildDate(0); | |
611 | if (m_commands) | |
612 | iBuildDate = m_commands->GetPersistedBuildDate(); | |
613 | ||
614 | return iBuildDate >= CEC_FW_DATE_EXTENDED_RESPONSE; | |
615 | } | |
616 | ||
617 | uint16_t CUSBCECAdapterCommunication::GetAdapterVendorId(void) const | |
618 | { | |
619 | return CEC_VID; | |
620 | } | |
621 | ||
622 | uint16_t CUSBCECAdapterCommunication::GetAdapterProductId(void) const | |
623 | { | |
624 | uint32_t iBuildDate(0); | |
625 | if (m_commands) | |
626 | iBuildDate = m_commands->GetPersistedBuildDate(); | |
627 | ||
628 | return iBuildDate >= CEC_FW_DATE_DESCRIPTOR2 ? CEC_PID2 : CEC_PID; | |
629 | } | |
630 | ||
631 | void CUSBCECAdapterCommunication::SetActiveSource(bool bSetTo, bool bClientUnregistered) | |
632 | { | |
633 | if (m_commands) | |
634 | m_commands->SetActiveSource(bSetTo, bClientUnregistered); | |
635 | } | |
636 | ||
637 | bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void) | |
638 | { | |
639 | return GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE && | |
640 | GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION; | |
641 | } | |
642 | ||
643 | bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration &configuration) | |
644 | { | |
645 | return IsOpen() ? | |
646 | m_commands->PersistConfiguration(configuration) && m_eepromWriteThread->Write() : | |
647 | false; | |
648 | } | |
649 | ||
650 | bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration &configuration) | |
651 | { | |
652 | return IsOpen() ? m_commands->GetConfiguration(configuration) : false; | |
653 | } | |
654 | ||
655 | std::string CUSBCECAdapterCommunication::GetPortName(void) | |
656 | { | |
657 | return m_port->GetName(); | |
658 | } | |
659 | ||
660 | bool CUSBCECAdapterCommunication::SetControlledMode(bool controlled) | |
661 | { | |
662 | return IsOpen() ? m_commands->SetControlledMode(controlled) : false; | |
663 | } | |
664 | ||
665 | uint16_t CUSBCECAdapterCommunication::GetPhysicalAddress(void) | |
666 | { | |
667 | uint16_t iPA(0); | |
668 | ||
669 | // try to get the PA from ADL | |
670 | #if defined(HAS_ADL_EDID_PARSER) | |
671 | { | |
672 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - trying to get the physical address via ADL", __FUNCTION__); | |
673 | CADLEdidParser adl; | |
674 | iPA = adl.GetPhysicalAddress(); | |
675 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - ADL returned physical address %04x", __FUNCTION__, iPA); | |
676 | } | |
677 | #endif | |
678 | ||
679 | // try to get the PA from the nvidia driver | |
680 | #if defined(HAS_NVIDIA_EDID_PARSER) | |
681 | if (iPA == 0) | |
682 | { | |
683 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - trying to get the physical address via nvidia driver", __FUNCTION__); | |
684 | CNVEdidParser nv; | |
685 | iPA = nv.GetPhysicalAddress(); | |
686 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - nvidia driver returned physical address %04x", __FUNCTION__, iPA); | |
687 | } | |
688 | #endif | |
689 | ||
690 | // try to get the PA from the OS | |
691 | if (iPA == 0) | |
692 | { | |
693 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - trying to get the physical address from the OS", __FUNCTION__); | |
694 | iPA = CEDIDParser::GetPhysicalAddress(); | |
695 | LIB_CEC->AddLog(CEC_LOG_DEBUG, "%s - OS returned physical address %04x", __FUNCTION__, iPA); | |
696 | } | |
697 | ||
698 | return iPA; | |
699 | } | |
700 | ||
701 | void *CAdapterPingThread::Process(void) | |
702 | { | |
703 | while (!IsStopped()) | |
704 | { | |
705 | if (m_timeout.TimeLeft() == 0) | |
706 | { | |
707 | /* reinit the timeout */ | |
708 | m_timeout.Init(CEC_ADAPTER_PING_TIMEOUT); | |
709 | ||
710 | /* send a ping to the adapter */ | |
711 | bool bPinged(false); | |
712 | int iFailedCounter(0); | |
713 | while (!bPinged && iFailedCounter < 3 && !IsStopped()) | |
714 | { | |
715 | if (!m_com->PingAdapter()) | |
716 | { | |
717 | /* sleep and retry */ | |
718 | Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT); | |
719 | ++iFailedCounter; | |
720 | } | |
721 | else | |
722 | { | |
723 | bPinged = true; | |
724 | } | |
725 | } | |
726 | ||
727 | if (iFailedCounter == 3 && !IsStopped()) | |
728 | { | |
729 | /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */ | |
730 | m_com->LIB_CEC->AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection."); | |
731 | m_com->StopThread(false); | |
732 | ||
733 | libcec_parameter param; | |
734 | param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN; | |
735 | m_com->LIB_CEC->Alert(CEC_ALERT_CONNECTION_LOST, param); | |
736 | ||
737 | break; | |
738 | } | |
739 | } | |
740 | ||
741 | Sleep(5); | |
742 | } | |
743 | return NULL; | |
744 | } | |
745 | ||
746 | void CAdapterEepromWriteThread::Stop(void) | |
747 | { | |
748 | StopThread(-1); | |
749 | { | |
750 | CLockObject lock(m_mutex); | |
751 | if (m_iScheduleEepromWrite > 0) | |
752 | m_com->LIB_CEC->AddLog(CEC_LOG_WARNING, "write thread stopped while a write was queued"); | |
753 | m_bWrite = true; | |
754 | m_condition.Signal(); | |
755 | } | |
756 | StopThread(); | |
757 | } | |
758 | ||
759 | void *CAdapterEepromWriteThread::Process(void) | |
760 | { | |
761 | while (!IsStopped()) | |
762 | { | |
763 | CLockObject lock(m_mutex); | |
764 | if ((m_iScheduleEepromWrite > 0 && m_iScheduleEepromWrite < GetTimeMs()) || | |
765 | m_condition.Wait(m_mutex, m_bWrite, 100)) | |
766 | { | |
767 | if (IsStopped()) | |
768 | break; | |
769 | m_bWrite = false; | |
770 | if (m_com->m_commands->WriteEEPROM()) | |
771 | { | |
772 | m_iLastEepromWrite = GetTimeMs(); | |
773 | m_iScheduleEepromWrite = 0; | |
774 | } | |
775 | else | |
776 | { | |
777 | m_iScheduleEepromWrite = GetTimeMs() + CEC_ADAPTER_EEPROM_WRITE_RETRY; | |
778 | } | |
779 | } | |
780 | } | |
781 | return NULL; | |
782 | } | |
783 | ||
784 | bool CAdapterEepromWriteThread::Write(void) | |
785 | { | |
786 | CLockObject lock(m_mutex); | |
787 | if (m_iScheduleEepromWrite == 0) | |
788 | { | |
789 | int64_t iNow = GetTimeMs(); | |
790 | if (m_iLastEepromWrite + CEC_ADAPTER_EEPROM_WRITE_INTERVAL > iNow) | |
791 | { | |
792 | m_com->LIB_CEC->AddLog(CEC_LOG_DEBUG, "delaying eeprom write by %ld ms", m_iLastEepromWrite + CEC_ADAPTER_EEPROM_WRITE_INTERVAL - iNow); | |
793 | m_iScheduleEepromWrite = m_iLastEepromWrite + CEC_ADAPTER_EEPROM_WRITE_INTERVAL; | |
794 | } | |
795 | else | |
796 | { | |
797 | m_bWrite = true; | |
798 | m_condition.Signal(); | |
799 | } | |
800 | } | |
801 | return true; | |
802 | } |