1 /*
2 * This file is part of the libCEC(R) library.
3 *
4 * libCEC(R) is Copyright (C) 2011-2015 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., 51 Franklin Street, Fifth Floor, Boston, MA
22 * 02110-1301 USA
23 *
24 *
25 * Alternatively, you can license this library under a commercial license,
26 * please contact Pulse-Eight Licensing for more information.
27 *
28 * For more information contact:
29 * Pulse-Eight Licensing <license@pulse-eight.com>
30 * http://www.pulse-eight.com/
31 * http://www.pulse-eight.net/
32 */
33
34 #include "env.h"
35 #include "USBCECAdapterDetection.h"
36
37 #if defined(__APPLE__)
38 #include <dirent.h>
39 #include <sys/param.h>
40 #include <IOKit/IOKitLib.h>
41 #include <IOKit/IOMessage.h>
42 #include <IOKit/IOCFPlugIn.h>
43 #include <IOKit/usb/IOUSBLib.h>
44 #include <IOKit/serial/IOSerialKeys.h>
45 #include <CoreFoundation/CoreFoundation.h>
46 #elif defined(__WINDOWS__)
47 #pragma comment(lib, "setupapi.lib")
48 #pragma comment(lib, "cfgmgr32.lib")
49 #include <setupapi.h>
50 #include <cfgmgr32.h>
51 #include <tchar.h>
52
53 // the virtual COM port only shows up when requesting devices with the raw device guid!
54 static GUID USB_RAW_GUID = { 0xA5DCBF10, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } };
55 static GUID USB_CDC_GUID = { 0x4D36E978, 0xE325, 0x11CE, { 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } };
56
57 #elif defined(HAVE_LIBUDEV)
58 #if defined(__FreeBSD__)
59 #include <sys/sysctl.h>
60 #endif
61 #include <dirent.h>
62 #include <poll.h>
63 extern "C" {
64 #include <libudev.h>
65 }
66 #elif defined(__FreeBSD__) || defined(__DragonFly__)
67 #include <sys/param.h>
68 #include <sys/sysctl.h>
69 #include <stdio.h>
70 #include <unistd.h>
71 #endif
72
73 #if defined(__linux__)
74 #include <dirent.h>
75 #include <ios>
76 #include <fstream>
77 #endif
78
79 #include <string>
80 #include <algorithm>
81 #include <stdio.h>
82 #include "p8-platform/util/StringUtils.h"
83
84 #define CEC_VID 0x2548
85 #define CEC_PID 0x1001
86 #define CEC_PID2 0x1002
87
88 using namespace CEC;
89
90 #if defined(HAVE_LIBUDEV) || defined(__linux__)
TranslateComPort(std::string & strString)91 bool TranslateComPort(std::string& strString)
92 {
93 std::string strTmp(strString);
94 std::reverse(strTmp.begin(), strTmp.end());
95 const char* iSlash = strchr(strTmp.c_str(), '/');
96 if (iSlash)
97 {
98 strTmp = StringUtils::Left(strTmp, iSlash - strTmp.c_str());
99 std::reverse(strTmp.begin(), strTmp.end());
100 strString = StringUtils::Format("%s/%s:1.0/tty", strString.c_str(), strTmp.c_str());
101 return true;
102 }
103
104 return false;
105 }
106
FindComPort(std::string & strLocation)107 bool FindComPort(std::string& strLocation)
108 {
109 std::string strPort = strLocation;
110 bool bReturn(!strPort.empty());
111 std::string strConfigLocation(strLocation);
112 if (TranslateComPort(strConfigLocation))
113 {
114 DIR *dir;
115 struct dirent *dirent;
116 if((dir = opendir(strConfigLocation.c_str())) == NULL)
117 return bReturn;
118
119 while ((dirent = readdir(dir)) != NULL)
120 {
121 if(strcmp((char*)dirent->d_name, "." ) != 0 && strcmp((char*)dirent->d_name, ".." ) != 0)
122 {
123 strPort = StringUtils::Format("/dev/%s", dirent->d_name);
124 if (!strPort.empty())
125 {
126 strLocation = strPort;
127 bReturn = true;
128 break;
129 }
130 }
131 }
132 closedir(dir);
133 }
134
135 return bReturn;
136 }
137 #endif
138
CanAutodetect(void)139 bool CUSBCECAdapterDetection::CanAutodetect(void)
140 {
141 #if defined(__APPLE__) || defined(HAVE_LIBUDEV) || defined(__WINDOWS__) || defined(__FreeBSD__) || defined(__linux__) || defined(__DragonFly__)
142 return true;
143 #else
144 return false;
145 #endif
146 }
147
148 #if defined(__WINDOWS__)
149 static DEVPROPKEY ADAPTER_LOCATION = { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14 };
150
GetComPortFromDevNode(DEVINST hDevInst,char * strPortName,unsigned int iSize)151 static bool GetComPortFromDevNode(DEVINST hDevInst, char* strPortName, unsigned int iSize)
152 {
153 WCHAR friendlyName[256];
154 WCHAR* portLocation;
155 DEVPROPTYPE PropertyType;
156 ULONG PropertySize;
157
158 // grab the com port from the device's friendly name
159 PropertySize = sizeof(friendlyName);
160 CM_Get_DevNode_PropertyW(hDevInst, &ADAPTER_LOCATION, &PropertyType, (PBYTE)friendlyName, &PropertySize, 0);
161 if (!!(portLocation = wcsstr(friendlyName, L"COM")))
162 {
163 std::string port;
164 char narrow[6];
165 size_t end;
166 snprintf(narrow, 6, "%ws", portLocation);
167 port = std::string(narrow);
168 if ((end = port.find(")")) != std::string::npos)
169 {
170 port = port.substr(0, end);
171 strncpy(strPortName, port.c_str(), iSize);
172 return true;
173 }
174 }
175
176 return false;
177 }
178
GetPidVidFromDeviceName(const std::string strDevName,int * vid,int * pid)179 static bool GetPidVidFromDeviceName(const std::string strDevName, int* vid, int* pid)
180 {
181 std::string strDevNameUpper(strDevName);
182 StringUtils::ToUpper(strDevNameUpper);
183 size_t iPidPos = strDevNameUpper.find("PID_");
184 size_t iVidPos = strDevNameUpper.find("VID_");
185 if (iPidPos == std::string::npos || iVidPos == std::string::npos || (strDevNameUpper.find("&MI_") != std::string::npos && strDevNameUpper.find("&MI_00") == std::string::npos))
186 return false;
187
188 std::string strVendorId(strDevNameUpper.substr(iVidPos + 4, 4));
189 std::string strProductId(strDevNameUpper.substr(iPidPos + 4, 4));
190
191 sscanf(strVendorId.c_str(), "%x", vid);
192 sscanf(strProductId.c_str(), "%x", pid);
193
194 return true;
195 }
196 #endif
197
FindAdaptersWindows(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)198 uint8_t CUSBCECAdapterDetection::FindAdaptersWindows(cec_adapter_descriptor* deviceList, uint8_t iBufSize, const char* strDevicePath /* = NULL */)
199 {
200 uint8_t iFound(0);
201
202 #if defined(__WINDOWS__)
203 ULONG len;
204 PCHAR buffer;
205
206 CM_Get_Device_ID_List_Size(&len, 0, CM_GETIDLIST_FILTER_NONE);
207 buffer = (PCHAR)malloc(sizeof(CHAR) * len);
208 if (buffer)
209 {
210 CM_Get_Device_ID_List(0, buffer, len, CM_GETIDLIST_FILTER_NONE);
211
212 for (CHAR* devId = buffer; *devId; devId += strlen(devId) + 1)
213 {
214 // check whether the path matches, if a path was given
215 if (strDevicePath && strcmp(strDevicePath, devId) != 0)
216 continue;
217
218 // get the vid and pid
219 int iVendor, iProduct;
220 if (!GetPidVidFromDeviceName(devId, &iVendor, &iProduct))
221 continue;
222
223 // no match
224 if (iVendor != CEC_VID || (iProduct != CEC_PID && iProduct != CEC_PID2))
225 continue;
226
227 // locate the device node
228 DEVINST devInst = 0, childInst = 0;
229 if (CM_Locate_DevNode(&devInst, devId, 0) != CR_SUCCESS)
230 continue;
231
232 // get the child node if this is a composite device
233 if (iProduct == CEC_PID2)
234 {
235 if (CM_Get_Child(&childInst, devInst, 0) != CR_SUCCESS)
236 continue;
237 devInst = childInst;
238 }
239
240 // get the com port
241 if (devInst != 0)
242 {
243 if (GetComPortFromDevNode(devInst, deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName)))
244 {
245 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", devId);
246 deviceList[iFound].iVendorId = (uint16_t)iVendor;
247 deviceList[iFound].iProductId = (uint16_t)iProduct;
248 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
249 iFound++;
250 }
251 }
252 }
253
254 free(buffer);
255 }
256 #else
257 (void)deviceList;
258 (void)iBufSize;
259 (void)strDevicePath;
260 #endif
261
262 return iFound;
263 }
264
FindAdaptersApple(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)265 uint8_t CUSBCECAdapterDetection::FindAdaptersApple(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
266 {
267 uint8_t iFound(0);
268
269 #if defined(__APPLE__)
270 kern_return_t kresult;
271 char bsdPath[MAXPATHLEN] = { 0 };
272 io_iterator_t serialPortIterator;
273
274 CFMutableDictionaryRef classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
275 if (classesToMatch)
276 {
277 CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes));
278 kresult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, &serialPortIterator);
279 if (kresult == KERN_SUCCESS)
280 {
281 io_object_t serialService;
282 while ((serialService = IOIteratorNext(serialPortIterator)))
283 {
284 int iVendor = 0, iProduct = 0;
285 CFTypeRef bsdPathAsCFString;
286
287 // fetch the device path.
288 bsdPathAsCFString = IORegistryEntryCreateCFProperty(serialService,
289 CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
290 if (bsdPathAsCFString)
291 {
292 // convert the path from a CFString to a C (NUL-terminated) string.
293 CFStringGetCString((CFStringRef)bsdPathAsCFString, bsdPath, MAXPATHLEN - 1, kCFStringEncodingUTF8);
294 CFRelease(bsdPathAsCFString);
295
296 // now walk up the hierarchy until we find the entry with vendor/product IDs
297 io_registry_entry_t parent;
298 CFTypeRef vendorIdAsCFNumber = NULL;
299 CFTypeRef productIdAsCFNumber = NULL;
300 kern_return_t kresult = IORegistryEntryGetParentEntry(serialService, kIOServicePlane, &parent);
301 while (kresult == KERN_SUCCESS)
302 {
303 vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
304 kIOServicePlane, CFSTR(kUSBVendorID), kCFAllocatorDefault, 0);
305 productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
306 kIOServicePlane, CFSTR(kUSBProductID), kCFAllocatorDefault, 0);
307 if (vendorIdAsCFNumber && productIdAsCFNumber)
308 {
309 CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberIntType, &iVendor);
310 CFRelease(vendorIdAsCFNumber);
311 CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberIntType, &iProduct);
312 CFRelease(productIdAsCFNumber);
313 IOObjectRelease(parent);
314 break;
315 }
316 io_registry_entry_t oldparent = parent;
317 kresult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent);
318 IOObjectRelease(oldparent);
319 }
320 if (strlen(bsdPath) && iVendor == CEC_VID && (iProduct == CEC_PID || iProduct == CEC_PID2))
321 {
322 if (!strDevicePath || !strcmp(bsdPath, strDevicePath))
323 {
324 // on darwin, the device path is the same as the comm path.
325 if (iFound == 0 || strcmp(deviceList[iFound - 1].strComName, bsdPath))
326 {
327 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", bsdPath);
328 snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", bsdPath);
329 deviceList[iFound].iVendorId = iVendor;
330 deviceList[iFound].iProductId = iProduct;
331 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
332 iFound++;
333 }
334 }
335 }
336 }
337 IOObjectRelease(serialService);
338 }
339 }
340 IOObjectRelease(serialPortIterator);
341 }
342 #else
343 (void)deviceList;
344 (void)iBufSize;
345 (void)strDevicePath;
346 #endif
347 return iFound;
348 }
349
FindAdaptersUdev(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)350 uint8_t CUSBCECAdapterDetection::FindAdaptersUdev(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
351 {
352 uint8_t iFound(0);
353
354 #if defined(HAVE_LIBUDEV)
355 struct udev *udev;
356 if (!(udev = udev_new()))
357 return -1;
358
359 struct udev_enumerate *enumerate;
360 struct udev_list_entry *devices, *dev_list_entry;
361 struct udev_device *dev, *pdev;
362 enumerate = udev_enumerate_new(udev);
363
364 udev_enumerate_add_match_subsystem(enumerate, "tty");
365 udev_enumerate_scan_devices(enumerate);
366 devices = udev_enumerate_get_list_entry(enumerate);
367 udev_list_entry_foreach(dev_list_entry, devices)
368 {
369 const char *strPath;
370 strPath = udev_list_entry_get_name(dev_list_entry);
371
372 dev = udev_device_new_from_syspath(udev, strPath);
373 if (!dev)
374 continue;
375
376 pdev = udev_device_get_parent(udev_device_get_parent(dev));
377 if (!pdev || !udev_device_get_sysattr_value(pdev, "idVendor") || !udev_device_get_sysattr_value(pdev, "idProduct"))
378 {
379 udev_device_unref(dev);
380 continue;
381 }
382
383 int iVendor, iProduct;
384 sscanf(udev_device_get_sysattr_value(pdev, "idVendor"), "%x", &iVendor);
385 sscanf(udev_device_get_sysattr_value(pdev, "idProduct"), "%x", &iProduct);
386 if (iVendor == CEC_VID && (iProduct == CEC_PID || iProduct == CEC_PID2))
387 {
388 std::string strPath(udev_device_get_syspath(pdev));
389 if (!strDevicePath || !strcmp(strPath.c_str(), strDevicePath))
390 {
391 std::string strComm(strPath);
392 if (FindComPort(strComm) && (iFound == 0 || strcmp(deviceList[iFound - 1].strComName, strComm.c_str())))
393 {
394 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", strPath.c_str());
395 snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", strComm.c_str());
396 deviceList[iFound].iVendorId = iVendor;
397 deviceList[iFound].iProductId = iProduct;
398 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
399 iFound++;
400 }
401 }
402 }
403 udev_device_unref(dev);
404
405 if (iFound >= iBufSize)
406 break;
407 }
408
409 udev_enumerate_unref(enumerate);
410 udev_unref(udev);
411 #else
412 (void)deviceList;
413 (void)iBufSize;
414 (void)strDevicePath;
415 #endif
416
417 return iFound;
418 }
419
FindAdaptersLinux(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)420 uint8_t CUSBCECAdapterDetection::FindAdaptersLinux(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
421 {
422 uint8_t iFound(0);
423
424 #if defined(__linux__)
425 std::string strSysfsPath("/sys/bus/usb/devices");
426 DIR *dir;
427
428 if ((dir = opendir(strSysfsPath.c_str())) != NULL)
429 {
430 struct dirent *dent;
431
432 while ((dent = readdir(dir)) != NULL)
433 {
434 std::string strDevice = StringUtils::Format("%s/%s", strSysfsPath.c_str(), dent->d_name);
435 unsigned int iVendor, iProduct;
436
437 if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
438 continue;
439
440 std::ifstream fVendor(StringUtils::Format("%s/idVendor", strDevice.c_str()));
441 if (!fVendor)
442 continue;
443 fVendor >> std::hex >> iVendor;
444
445 std::ifstream fProduct(StringUtils::Format("%s/idProduct", strDevice.c_str()));
446 if (!fProduct)
447 continue;
448 fProduct >> std::hex >> iProduct;
449
450 if (iVendor != CEC_VID || (iProduct != CEC_PID && iProduct != CEC_PID2))
451 continue;
452
453 if (strDevicePath && strcmp(strDevice.c_str(), strDevicePath))
454 continue;
455
456 std::string strPort(strDevice);
457 if (FindComPort(strPort) && (iFound == 0 || strcmp(deviceList[iFound - 1].strComName, strPort.c_str())))
458 {
459 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", strDevice.c_str());
460 snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", strPort.c_str());
461 deviceList[iFound].iVendorId = iVendor;
462 deviceList[iFound].iProductId = iProduct;
463 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
464 iFound++;
465 }
466
467 if (iFound >= iBufSize)
468 break;
469 }
470
471 closedir(dir);
472 }
473
474 #else
475 (void)deviceList;
476 (void)iBufSize;
477 (void)strDevicePath;
478 #endif
479
480 return iFound;
481 }
482
FindAdaptersFreeBSD(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)483 uint8_t CUSBCECAdapterDetection::FindAdaptersFreeBSD(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
484 {
485 uint8_t iFound(0);
486
487 #if defined(__FreeBSD__) || defined(__DragonFly__)
488 char devicePath[PATH_MAX + 1];
489 char infos[512];
490 char sysctlname[32];
491 char ttyname[8];
492 char *pos;
493 size_t infos_size = sizeof(infos);
494 int i;
495
496 for (i = 0;; ++i)
497 {
498 unsigned int iVendor, iProduct;
499 memset(infos, 0, sizeof(infos));
500 (void)snprintf(sysctlname, sizeof(sysctlname),
501 "dev.umodem.%d.%%pnpinfo", i);
502 if (sysctlbyname(sysctlname, infos, &infos_size,
503 NULL, 0) != 0)
504 break;
505 pos = strstr(infos, "vendor=");
506 if (pos == NULL)
507 continue;
508 sscanf(pos, "vendor=%x ", &iVendor);
509
510 pos = strstr(infos, "product=");
511 if (pos == NULL)
512 continue;
513 sscanf(pos, "product=%x ", &iProduct);
514
515 if (iVendor != CEC_VID || (iProduct != CEC_PID && iProduct != CEC_PID2))
516 continue;
517
518 pos = strstr(infos, "ttyname=");
519 if (pos == NULL)
520 continue;
521 sscanf(pos, "ttyname=%s ", ttyname);
522
523 (void)snprintf(devicePath, sizeof(devicePath),
524 "/dev/tty%s", ttyname);
525
526 if (strDevicePath) {
527 char currStrDevicePath[512];
528 int port = 0;
529 int devaddr = 0;
530 memset(currStrDevicePath, 0, sizeof(currStrDevicePath));
531 memset(infos, 0, sizeof(infos));
532 (void)snprintf(sysctlname, sizeof(sysctlname),
533 "dev.umodem.%d.%%location", i);
534 if (sysctlbyname(sysctlname, infos, &infos_size,
535 NULL, 0) != 0)
536 break;
537
538 pos = strstr(infos, "port=");
539 if (pos == NULL)
540 continue;
541 sscanf(pos, "port=%d ", &port);
542
543 pos = strstr(infos, "devaddr=");
544 if (pos == NULL)
545 continue;
546 sscanf(pos, "devaddr=%d ", &devaddr);
547
548 (void)snprintf(currStrDevicePath, sizeof(currStrDevicePath),
549 "/dev/ugen%d.%d", port, devaddr);
550
551 if (strcmp(currStrDevicePath, strDevicePath) != 0)
552 continue;
553 }
554 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", devicePath);
555 snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", devicePath);
556 deviceList[iFound].iVendorId = iVendor;
557 deviceList[iFound].iProductId = iProduct;
558 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
559 iFound++;
560 }
561 #else
562 (void)deviceList;
563 (void)iBufSize;
564 (void)strDevicePath;
565 #endif
566
567 return iFound;
568 }
569
FindAdapters(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)570 uint8_t CUSBCECAdapterDetection::FindAdapters(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
571 {
572 uint8_t iFound(0);
573 iFound = FindAdaptersApple(deviceList, iBufSize, strDevicePath);
574 if (iFound == 0)
575 iFound = FindAdaptersFreeBSD(deviceList, iBufSize, strDevicePath);
576 if (iFound == 0)
577 iFound = FindAdaptersUdev(deviceList, iBufSize, strDevicePath);
578 if (iFound == 0)
579 iFound = FindAdaptersLinux(deviceList, iBufSize, strDevicePath);
580 if (iFound == 0)
581 iFound = FindAdaptersWindows(deviceList, iBufSize, strDevicePath);
582 return iFound;
583 }
584