2 * This file is part of the libCEC(R) library.
4 * libCEC(R) is Copyright (C) 2011-2013 Pulse-Eight Limited. All rights reserved.
5 * libCEC(R) is an original work, containing original code.
7 * libCEC(R) is a trademark of Pulse-Eight Limited.
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.
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.
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.
24 * Alternatively, you can license this library under a commercial license,
25 * please contact Pulse-Eight Licensing for more information.
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/
34 #include "USBCECAdapterDetection.h"
35 #include "lib/platform/util/StdString.h"
37 #if defined(__APPLE__)
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")
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 } };
57 #elif defined(HAVE_LIBUDEV)
63 #elif defined(__FreeBSD__)
64 #include <sys/param.h>
65 #include <sys/sysctl.h>
70 #define CEC_VID 0x2548
71 #define CEC_PID 0x1001
72 #define CEC_PID2 0x1002
77 #if defined(HAVE_LIBUDEV)
78 bool TranslateComPort(CStdString
&strString
)
80 CStdString
strTmp(strString
);
82 int iSlash
= strTmp
.Find('/');
85 strTmp
= strTmp
.Left(iSlash
);
87 strString
.Format("%s/%s:1.0/tty", strString
.c_str(), strTmp
.c_str());
94 bool FindComPort(CStdString
&strLocation
)
96 CStdString strPort
= strLocation
;
97 bool bReturn(!strPort
.IsEmpty());
98 CStdString
strConfigLocation(strLocation
);
99 if (TranslateComPort(strConfigLocation
))
102 struct dirent
*dirent
;
103 if((dir
= opendir(strConfigLocation
.c_str())) == NULL
)
106 while ((dirent
= readdir(dir
)) != NULL
)
108 if(strcmp((char*)dirent
->d_name
, "." ) != 0 && strcmp((char*)dirent
->d_name
, ".." ) != 0)
110 strPort
.Format("/dev/%s", dirent
->d_name
);
111 if (!strPort
.IsEmpty())
113 strLocation
= strPort
;
126 bool CUSBCECAdapterDetection::CanAutodetect(void)
128 #if defined(__APPLE__) || defined(HAVE_LIBUDEV) || defined(__WINDOWS__) || defined(__FreeBSD__)
135 #if defined(__WINDOWS__)
136 static bool GetComPortFromHandle(HDEVINFO hDevHandle
, PSP_DEVINFO_DATA devInfoData
, char* strPortName
, unsigned int iSize
)
139 TCHAR strRegPortName
[256];
140 strRegPortName
[0] = _T('\0');
141 DWORD dwSize
= sizeof(strRegPortName
);
144 HKEY hDeviceKey
= SetupDiOpenDevRegKey(hDevHandle
, devInfoData
, DICS_FLAG_GLOBAL
, 0, DIREG_DEV
, KEY_QUERY_VALUE
);
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)
155 // return the port name
156 snprintf(strPortName
, iSize
, "%s", strRegPortName
);
160 RegCloseKey(hDeviceKey
);
165 static bool FindComPortForComposite(const char* strLocation
, char* strPortName
, unsigned int iSize
)
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
)
174 // check all devices, whether they match the location or not
177 for (int iPtr
= 0; !bReturn
&& bGetNext
&& iPtr
< 1024 ; iPtr
++)
181 SP_DEVINFO_DATA devInfoData
;
182 devInfoData
.cbSize
= sizeof(SP_DEVINFO_DATA
);
185 if (!SetupDiEnumDeviceInfo(hDevHandle
, iPtr
, &devInfoData
))
189 // check if the location of the _parent_ device matches
190 DEVINST parentDevInst
;
191 if (CM_Get_Parent(&parentDevInst
, devInfoData
.DevInst
, 0) == CR_SUCCESS
)
193 CM_Get_Device_ID(parentDevInst
, strId
, 512, 0);
196 if (!strncmp(strId
, strLocation
, strlen(strLocation
)))
197 bReturn
= GetComPortFromHandle(hDevHandle
, &devInfoData
, strPortName
, iSize
);
202 SetupDiDestroyDeviceInfoList(hDevHandle
);
207 uint8_t CUSBCECAdapterDetection::FindAdapters(cec_adapter_descriptor
*deviceList
, uint8_t iBufSize
, const char *strDevicePath
/* = NULL */)
211 #if defined(__APPLE__)
212 kern_return_t kresult
;
213 char bsdPath
[MAXPATHLEN
] = {0};
214 io_iterator_t serialPortIterator
;
216 CFMutableDictionaryRef classesToMatch
= IOServiceMatching(kIOSerialBSDServiceValue
);
219 CFDictionarySetValue(classesToMatch
, CFSTR(kIOSerialBSDTypeKey
), CFSTR(kIOSerialBSDModemType
));
220 kresult
= IOServiceGetMatchingServices(kIOMasterPortDefault
, classesToMatch
, &serialPortIterator
);
221 if (kresult
== KERN_SUCCESS
)
223 io_object_t serialService
;
224 while ((serialService
= IOIteratorNext(serialPortIterator
)))
226 int iVendor
= 0, iProduct
= 0;
227 CFTypeRef bsdPathAsCFString
;
229 // fetch the device path.
230 bsdPathAsCFString
= IORegistryEntryCreateCFProperty(serialService
,
231 CFSTR(kIOCalloutDeviceKey
), kCFAllocatorDefault
, 0);
232 if (bsdPathAsCFString
)
234 // convert the path from a CFString to a C (NUL-terminated) string.
235 CFStringGetCString((CFStringRef
)bsdPathAsCFString
, bsdPath
, MAXPATHLEN
- 1, kCFStringEncodingUTF8
);
236 CFRelease(bsdPathAsCFString
);
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
)
245 vendorIdAsCFNumber
= IORegistryEntrySearchCFProperty(parent
,
246 kIOServicePlane
, CFSTR(kUSBVendorID
), kCFAllocatorDefault
, 0);
247 productIdAsCFNumber
= IORegistryEntrySearchCFProperty(parent
,
248 kIOServicePlane
, CFSTR(kUSBProductID
), kCFAllocatorDefault
, 0);
249 if (vendorIdAsCFNumber
&& productIdAsCFNumber
)
251 CFNumberGetValue((CFNumberRef
)vendorIdAsCFNumber
, kCFNumberIntType
, &iVendor
);
252 CFRelease(vendorIdAsCFNumber
);
253 CFNumberGetValue((CFNumberRef
)productIdAsCFNumber
, kCFNumberIntType
, &iProduct
);
254 CFRelease(productIdAsCFNumber
);
255 IOObjectRelease(parent
);
258 io_registry_entry_t oldparent
= parent
;
259 kresult
= IORegistryEntryGetParentEntry(parent
, kIOServicePlane
, &parent
);
260 IOObjectRelease(oldparent
);
262 if (strlen(bsdPath
) && iVendor
== CEC_VID
&& (iProduct
== CEC_PID
|| iProduct
== CEC_PID2
))
264 if (!strDevicePath
|| !strcmp(bsdPath
, strDevicePath
))
266 // on darwin, the device path is the same as the comm path.
267 if (iFound
== 0 || strcmp(deviceList
[iFound
-1].strComName
, bsdPath
))
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
279 IOObjectRelease(serialService
);
282 IOObjectRelease(serialPortIterator
);
284 #elif defined(HAVE_LIBUDEV)
286 if (!(udev
= udev_new()))
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
)
298 strPath
= udev_list_entry_get_name(dev_list_entry
);
300 dev
= udev_device_new_from_syspath(udev
, strPath
);
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"))
307 udev_device_unref(dev
);
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
))
316 CStdString
strPath(udev_device_get_syspath(pdev
));
317 if (!strDevicePath
|| !strcmp(strPath
.c_str(), strDevicePath
))
319 CStdString
strComm(strPath
);
320 if (FindComPort(strComm
) && (iFound
== 0 || strcmp(deviceList
[iFound
-1].strComName
, strComm
.c_str())))
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
331 udev_device_unref(dev
);
334 udev_enumerate_unref(enumerate
);
336 #elif defined(__WINDOWS__)
338 DWORD required
= 0, iMemberIndex
= 0;
341 SP_DEVICE_INTERFACE_DATA deviceInterfaceData
;
342 deviceInterfaceData
.cbSize
= sizeof(SP_DEVICE_INTERFACE_DATA
);
344 SP_DEVINFO_DATA devInfoData
;
345 devInfoData
.cbSize
= sizeof(SP_DEVINFO_DATA
);
348 if ((hDevHandle
= SetupDiGetClassDevs(&USB_RAW_GUID
, 0, 0, DIGCF_PRESENT
| DIGCF_DEVICEINTERFACE
)) == INVALID_HANDLE_VALUE
)
352 TCHAR
*buffer
= NULL
;
353 PSP_DEVICE_INTERFACE_DETAIL_DATA devicedetailData
;
354 while(bResult
&& iFound
< iBufSize
)
356 bResult
= SetupDiEnumDeviceInfo(hDevHandle
, iMemberIndex
, &devInfoData
);
359 bResult
= SetupDiEnumDeviceInterfaces(hDevHandle
, 0, &USB_RAW_GUID
, iMemberIndex
, &deviceInterfaceData
);
364 SetupDiDestroyDeviceInfoList(hDevHandle
);
371 BOOL bDetailResult
= false;
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.
380 SetupDiGetDeviceInterfaceDetail(hDevHandle
, &deviceInterfaceData
, NULL
, 0, &required
, NULL
);
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
;
388 bDetailResult
= SetupDiGetDeviceInterfaceDetail(hDevHandle
, &deviceInterfaceData
, devicedetailData
, nBufferSize
, &required
, NULL
);
392 // check whether the path matches, if a path was given
393 if (strDevicePath
&& strcmp(strDevicePath
, devicedetailData
->DevicePath
) != 0)
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)
405 int iVendor
, iProduct
;
406 sscanf(strVendorId
, "%x", &iVendor
);
407 sscanf(strProductId
, "%x", &iProduct
);
410 if (iVendor
!= CEC_VID
|| (iProduct
!= CEC_PID
&& iProduct
!= CEC_PID2
))
414 if (iProduct
== CEC_PID2
)
416 // the 1002 pid indicates a composite device, that needs special treatment
418 CM_Get_Device_ID(devInfoData
.DevInst
, strId
, 512, 0);
419 if (FindComPortForComposite(strId
, deviceList
[iFound
].strComName
, sizeof(deviceList
[iFound
].strComName
)))
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
428 else if (GetComPortFromHandle(hDevHandle
, &devInfoData
, deviceList
[iFound
].strComName
, sizeof(deviceList
[iFound
].strComName
)))
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
437 #elif defined(__FreeBSD__)
438 char devicePath
[PATH_MAX
+ 1];
443 size_t infos_size
= sizeof(infos
);
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
,
455 pos
= strstr(infos
, "vendor=");
458 sscanf(pos
, "vendor=%x ", &iVendor
);
460 pos
= strstr(infos
, "product=");
463 sscanf(pos
, "product=%x ", &iProduct
);
465 if (iVendor
!= CEC_VID
|| (iProduct
!= CEC_PID
&& iProduct
!= CEC_PID2
))
468 pos
= strstr(infos
, "ttyname=");
471 sscanf(pos
, "ttyname=%s ", ttyname
);
473 (void)snprintf(devicePath
, sizeof(devicePath
),
474 "/dev/tty%s", ttyname
);
477 char currStrDevicePath
[512];
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
,
488 pos
= strstr(infos
, "port=");
491 sscanf(pos
, "port=%d ", &port
);
493 pos
= strstr(infos
, "devaddr=");
496 sscanf(pos
, "devaddr=%d ", &devaddr
);
498 (void)snprintf(currStrDevicePath
, sizeof(currStrDevicePath
),
499 "/dev/ugen%d.%d", port
, devaddr
);
501 if (strcmp(currStrDevicePath
, strDevicePath
) != 0)
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
512 //silence "unused" warnings
514 ((void) strDevicePath
);
517 iBufSize
= 0; if(!iBufSize
){} /* silence "unused" warning on linux/osx */