58bf4a562abd17697a85c15e5fe6bf99e2d237e9
[deb_libcec.git] / src / lib / adapter / Pulse-Eight / USBCECAdapterDetection.cpp
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 "USBCECAdapterDetection.h"
35 #include "lib/platform/util/StdString.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, "advapi32.lib")
48 #pragma comment(lib, "setupapi.lib")
49 #pragma comment(lib, "cfgmgr32.lib")
50 #include <setupapi.h>
51 #include <cfgmgr32.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 #include <dirent.h>
59 #include <poll.h>
60 extern "C" {
61 #include <libudev.h>
62 }
63 #elif defined(__FreeBSD__)
64 #include <sys/param.h>
65 #include <sys/sysctl.h>
66 #include <stdio.h>
67 #include <unistd.h>
68 #endif
69
70 #define CEC_VID 0x2548
71 #define CEC_PID 0x1001
72 #define CEC_PID2 0x1002
73
74 using namespace CEC;
75 using namespace std;
76
77 #if defined(HAVE_LIBUDEV)
78 bool TranslateComPort(CStdString &strString)
79 {
80 CStdString strTmp(strString);
81 strTmp.MakeReverse();
82 int iSlash = strTmp.Find('/');
83 if (iSlash >= 0)
84 {
85 strTmp = strTmp.Left(iSlash);
86 strTmp.MakeReverse();
87 strString.Format("%s/%s:1.0/tty", strString.c_str(), strTmp.c_str());
88 return true;
89 }
90
91 return false;
92 }
93
94 bool FindComPort(CStdString &strLocation)
95 {
96 CStdString strPort = strLocation;
97 bool bReturn(!strPort.IsEmpty());
98 CStdString strConfigLocation(strLocation);
99 if (TranslateComPort(strConfigLocation))
100 {
101 DIR *dir;
102 struct dirent *dirent;
103 if((dir = opendir(strConfigLocation.c_str())) == NULL)
104 return bReturn;
105
106 while ((dirent = readdir(dir)) != NULL)
107 {
108 if(strcmp((char*)dirent->d_name, "." ) != 0 && strcmp((char*)dirent->d_name, ".." ) != 0)
109 {
110 strPort.Format("/dev/%s", dirent->d_name);
111 if (!strPort.IsEmpty())
112 {
113 strLocation = strPort;
114 bReturn = true;
115 break;
116 }
117 }
118 }
119 closedir(dir);
120 }
121
122 return bReturn;
123 }
124 #endif
125
126 bool CUSBCECAdapterDetection::CanAutodetect(void)
127 {
128 #if defined(__APPLE__) || defined(HAVE_LIBUDEV) || defined(__WINDOWS__) || defined(__FreeBSD__)
129 return true;
130 #else
131 return false;
132 #endif
133 }
134
135 #if defined(__WINDOWS__)
136 static bool GetComPortFromHandle(HDEVINFO hDevHandle, PSP_DEVINFO_DATA devInfoData, char* strPortName, unsigned int iSize)
137 {
138 bool bReturn(false);
139 TCHAR strRegPortName[256];
140 strRegPortName[0] = _T('\0');
141 DWORD dwSize = sizeof(strRegPortName);
142 DWORD dwType = 0;
143
144 HKEY hDeviceKey = SetupDiOpenDevRegKey(hDevHandle, devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
145 if (!hDeviceKey)
146 return bReturn;
147
148 // locate the PortName
149 if ((RegQueryValueEx(hDeviceKey, _T("PortName"), NULL, &dwType, reinterpret_cast<LPBYTE>(strRegPortName), &dwSize) == ERROR_SUCCESS) &&
150 (dwType == REG_SZ) &&
151 _tcslen(strRegPortName) > 3 &&
152 _tcsnicmp(strRegPortName, _T("COM"), 3) == 0 &&
153 _ttoi(&(strRegPortName[3])) > 0)
154 {
155 // return the port name
156 snprintf(strPortName, iSize, "%s", strRegPortName);
157 bReturn = true;
158 }
159
160 RegCloseKey(hDeviceKey);
161
162 return bReturn;
163 }
164
165 static bool FindComPortForComposite(const char* strLocation, char* strPortName, unsigned int iSize)
166 {
167 bool bReturn(false);
168
169 // find all devices of the CDC class
170 HDEVINFO hDevHandle = SetupDiGetClassDevs(&USB_CDC_GUID, NULL, NULL, DIGCF_PRESENT);
171 if (hDevHandle == INVALID_HANDLE_VALUE)
172 return bReturn;
173
174 // check all devices, whether they match the location or not
175 char strId[512];
176 bool bGetNext(true);
177 for (int iPtr = 0; !bReturn && bGetNext && iPtr < 1024 ; iPtr++)
178 {
179 strId[0] = 0;
180
181 SP_DEVINFO_DATA devInfoData;
182 devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
183
184 // no more devices
185 if (!SetupDiEnumDeviceInfo(hDevHandle, iPtr, &devInfoData))
186 bGetNext = false;
187 else
188 {
189 // check if the location of the _parent_ device matches
190 DEVINST parentDevInst;
191 if (CM_Get_Parent(&parentDevInst, devInfoData.DevInst, 0) == CR_SUCCESS)
192 {
193 CM_Get_Device_ID(parentDevInst, strId, 512, 0);
194
195 // match
196 if (!strncmp(strId, strLocation, strlen(strLocation)))
197 bReturn = GetComPortFromHandle(hDevHandle, &devInfoData, strPortName, iSize);
198 }
199 }
200 }
201
202 SetupDiDestroyDeviceInfoList(hDevHandle);
203 return bReturn;
204 }
205 #endif
206
207 uint8_t CUSBCECAdapterDetection::FindAdapters(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
208 {
209 uint8_t iFound(0);
210
211 #if defined(__APPLE__)
212 kern_return_t kresult;
213 char bsdPath[MAXPATHLEN] = {0};
214 io_iterator_t serialPortIterator;
215
216 CFMutableDictionaryRef classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
217 if (classesToMatch)
218 {
219 CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType));
220 kresult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, &serialPortIterator);
221 if (kresult == KERN_SUCCESS)
222 {
223 io_object_t serialService;
224 while ((serialService = IOIteratorNext(serialPortIterator)))
225 {
226 int iVendor = 0, iProduct = 0;
227 CFTypeRef bsdPathAsCFString;
228
229 // fetch the device path.
230 bsdPathAsCFString = IORegistryEntryCreateCFProperty(serialService,
231 CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
232 if (bsdPathAsCFString)
233 {
234 // convert the path from a CFString to a C (NUL-terminated) string.
235 CFStringGetCString((CFStringRef)bsdPathAsCFString, bsdPath, MAXPATHLEN - 1, kCFStringEncodingUTF8);
236 CFRelease(bsdPathAsCFString);
237
238 // now walk up the hierarchy until we find the entry with vendor/product IDs
239 io_registry_entry_t parent;
240 CFTypeRef vendorIdAsCFNumber = NULL;
241 CFTypeRef productIdAsCFNumber = NULL;
242 kern_return_t kresult = IORegistryEntryGetParentEntry(serialService, kIOServicePlane, &parent);
243 while (kresult == KERN_SUCCESS)
244 {
245 vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
246 kIOServicePlane, CFSTR(kUSBVendorID), kCFAllocatorDefault, 0);
247 productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
248 kIOServicePlane, CFSTR(kUSBProductID), kCFAllocatorDefault, 0);
249 if (vendorIdAsCFNumber && productIdAsCFNumber)
250 {
251 CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberIntType, &iVendor);
252 CFRelease(vendorIdAsCFNumber);
253 CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberIntType, &iProduct);
254 CFRelease(productIdAsCFNumber);
255 IOObjectRelease(parent);
256 break;
257 }
258 io_registry_entry_t oldparent = parent;
259 kresult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent);
260 IOObjectRelease(oldparent);
261 }
262 if (strlen(bsdPath) && iVendor == CEC_VID && (iProduct == CEC_PID || iProduct == CEC_PID2))
263 {
264 if (!strDevicePath || !strcmp(bsdPath, strDevicePath))
265 {
266 // on darwin, the device path is the same as the comm path.
267 if (iFound == 0 || strcmp(deviceList[iFound-1].strComName, bsdPath))
268 {
269 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", bsdPath);
270 snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", bsdPath);
271 deviceList[iFound].iVendorId = iVendor;
272 deviceList[iFound].iProductId = iProduct;
273 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
274 iFound++;
275 }
276 }
277 }
278 }
279 IOObjectRelease(serialService);
280 }
281 }
282 IOObjectRelease(serialPortIterator);
283 }
284 #elif defined(HAVE_LIBUDEV)
285 struct udev *udev;
286 if (!(udev = udev_new()))
287 return -1;
288
289 struct udev_enumerate *enumerate;
290 struct udev_list_entry *devices, *dev_list_entry;
291 struct udev_device *dev, *pdev;
292 enumerate = udev_enumerate_new(udev);
293 udev_enumerate_scan_devices(enumerate);
294 devices = udev_enumerate_get_list_entry(enumerate);
295 udev_list_entry_foreach(dev_list_entry, devices)
296 {
297 const char *strPath;
298 strPath = udev_list_entry_get_name(dev_list_entry);
299
300 dev = udev_device_new_from_syspath(udev, strPath);
301 if (!dev)
302 continue;
303
304 pdev = udev_device_get_parent(udev_device_get_parent(dev));
305 if (!pdev || !udev_device_get_sysattr_value(pdev,"idVendor") || !udev_device_get_sysattr_value(pdev, "idProduct"))
306 {
307 udev_device_unref(dev);
308 continue;
309 }
310
311 int iVendor, iProduct;
312 sscanf(udev_device_get_sysattr_value(pdev, "idVendor"), "%x", &iVendor);
313 sscanf(udev_device_get_sysattr_value(pdev, "idProduct"), "%x", &iProduct);
314 if (iVendor == CEC_VID && (iProduct == CEC_PID || iProduct == CEC_PID2))
315 {
316 CStdString strPath(udev_device_get_syspath(pdev));
317 if (!strDevicePath || !strcmp(strPath.c_str(), strDevicePath))
318 {
319 CStdString strComm(strPath);
320 if (FindComPort(strComm) && (iFound == 0 || strcmp(deviceList[iFound-1].strComName, strComm.c_str())))
321 {
322 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", strPath.c_str());
323 snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", strComm.c_str());
324 deviceList[iFound].iVendorId = iVendor;
325 deviceList[iFound].iProductId = iProduct;
326 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
327 iFound++;
328 }
329 }
330 }
331 udev_device_unref(dev);
332 }
333
334 udev_enumerate_unref(enumerate);
335 udev_unref(udev);
336 #elif defined(__WINDOWS__)
337 HDEVINFO hDevHandle;
338 DWORD required = 0, iMemberIndex = 0;
339 int nBufferSize = 0;
340
341 SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
342 deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
343
344 SP_DEVINFO_DATA devInfoData;
345 devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
346
347 // find all devices
348 if ((hDevHandle = SetupDiGetClassDevs(&USB_RAW_GUID, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)) == INVALID_HANDLE_VALUE)
349 return iFound;
350
351 BOOL bResult = true;
352 TCHAR *buffer = NULL;
353 PSP_DEVICE_INTERFACE_DETAIL_DATA devicedetailData;
354 while(bResult && iFound < iBufSize)
355 {
356 bResult = SetupDiEnumDeviceInfo(hDevHandle, iMemberIndex, &devInfoData);
357
358 if (bResult)
359 bResult = SetupDiEnumDeviceInterfaces(hDevHandle, 0, &USB_RAW_GUID, iMemberIndex, &deviceInterfaceData);
360
361 if(!bResult)
362 {
363 // no (more) results
364 SetupDiDestroyDeviceInfoList(hDevHandle);
365 delete []buffer;
366 buffer = NULL;
367 return iFound;
368 }
369
370 iMemberIndex++;
371 BOOL bDetailResult = false;
372 {
373 // As per MSDN, Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with a
374 // NULL DeviceInterfaceDetailData pointer, a DeviceInterfaceDetailDataSize of zero,
375 // and a valid RequiredSize variable. In response to such a call, this function returns
376 // the required buffer size at RequiredSize and fails with GetLastError returning
377 // ERROR_INSUFFICIENT_BUFFER.
378 // Allocate an appropriately sized buffer and call the function again to get the interface details.
379
380 SetupDiGetDeviceInterfaceDetail(hDevHandle, &deviceInterfaceData, NULL, 0, &required, NULL);
381
382 buffer = new TCHAR[required];
383 devicedetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) buffer;
384 devicedetailData->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
385 nBufferSize = required;
386 }
387
388 bDetailResult = SetupDiGetDeviceInterfaceDetail(hDevHandle, &deviceInterfaceData, devicedetailData, nBufferSize , &required, NULL);
389 if(!bDetailResult)
390 continue;
391
392 // check whether the path matches, if a path was given
393 if (strDevicePath && strcmp(strDevicePath, devicedetailData->DevicePath) != 0)
394 continue;
395
396 // get the vid and pid
397 CStdString strVendorId;
398 CStdString strProductId;
399 CStdString strTmp(devicedetailData->DevicePath);
400 strVendorId.assign(strTmp.substr(strTmp.Find("vid_") + 4, 4));
401 strProductId.assign(strTmp.substr(strTmp.Find("pid_") + 4, 4));
402 if (strTmp.Find("&mi_") >= 0 && strTmp.Find("&mi_00") < 0)
403 continue;
404
405 int iVendor, iProduct;
406 sscanf(strVendorId, "%x", &iVendor);
407 sscanf(strProductId, "%x", &iProduct);
408
409 // no match
410 if (iVendor != CEC_VID || (iProduct != CEC_PID && iProduct != CEC_PID2))
411 continue;
412
413
414 if (iProduct == CEC_PID2)
415 {
416 // the 1002 pid indicates a composite device, that needs special treatment
417 char strId[512];
418 CM_Get_Device_ID(devInfoData.DevInst, strId, 512, 0);
419 if (FindComPortForComposite(strId, deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName)))
420 {
421 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", devicedetailData->DevicePath);
422 deviceList[iFound].iVendorId = (uint16_t)iVendor;
423 deviceList[iFound].iProductId = (uint16_t)iProduct;
424 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
425 iFound++;
426 }
427 }
428 else if (GetComPortFromHandle(hDevHandle, &devInfoData, deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName)))
429 {
430 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", devicedetailData->DevicePath);
431 deviceList[iFound].iVendorId = (uint16_t)iVendor;
432 deviceList[iFound].iProductId = (uint16_t)iProduct;
433 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
434 iFound++;
435 }
436 }
437 #elif defined(__FreeBSD__)
438 char devicePath[PATH_MAX + 1];
439 char infos[512];
440 char sysctlname[32];
441 char ttyname[8];
442 char *pos;
443 size_t infos_size = sizeof(infos);
444 int i;
445
446 for (i = 0; ; ++i)
447 {
448 unsigned int iVendor, iProduct;
449 memset(infos, 0, sizeof(infos));
450 (void)snprintf(sysctlname, sizeof(sysctlname),
451 "dev.umodem.%d.%%pnpinfo", i);
452 if (sysctlbyname(sysctlname, infos, &infos_size,
453 NULL, 0) != 0)
454 break;
455 pos = strstr(infos, "vendor=");
456 if (pos == NULL)
457 continue;
458 sscanf(pos, "vendor=%x ", &iVendor);
459
460 pos = strstr(infos, "product=");
461 if (pos == NULL)
462 continue;
463 sscanf(pos, "product=%x ", &iProduct);
464
465 if (iVendor != CEC_VID || (iProduct != CEC_PID && iProduct != CEC_PID2))
466 continue;
467
468 pos = strstr(infos, "ttyname=");
469 if (pos == NULL)
470 continue;
471 sscanf(pos, "ttyname=%s ", ttyname);
472
473 (void)snprintf(devicePath, sizeof(devicePath),
474 "/dev/tty%s", ttyname);
475
476 if (strDevicePath) {
477 char currStrDevicePath[512];
478 int port = 0;
479 int devaddr = 0;
480 memset(currStrDevicePath, 0, sizeof(currStrDevicePath));
481 memset(infos, 0, sizeof(infos));
482 (void)snprintf(sysctlname, sizeof(sysctlname),
483 "dev.umodem.%d.%%location", i);
484 if (sysctlbyname(sysctlname, infos, &infos_size,
485 NULL, 0) != 0)
486 break;
487
488 pos = strstr(infos, "port=");
489 if (pos == NULL)
490 continue;
491 sscanf(pos, "port=%d ", &port);
492
493 pos = strstr(infos, "devaddr=");
494 if (pos == NULL)
495 continue;
496 sscanf(pos, "devaddr=%d ", &devaddr);
497
498 (void)snprintf(currStrDevicePath, sizeof(currStrDevicePath),
499 "/dev/ugen%d.%d", port, devaddr);
500
501 if (strcmp(currStrDevicePath, strDevicePath) != 0)
502 continue;
503 }
504 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", devicePath);
505 snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", devicePath);
506 deviceList[iFound].iVendorId = iVendor;
507 deviceList[iFound].iProductId = iProduct;
508 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
509 iFound++;
510 }
511 #else
512 //silence "unused" warnings
513 ((void)deviceList);
514 ((void) strDevicePath);
515 #endif
516
517 iBufSize = 0; if(!iBufSize){} /* silence "unused" warning on linux/osx */
518
519 return iFound;
520 }