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