f9969d83c08c16955250c5e867641751a4eb4cad
[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-2012 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 #include <setupapi.h>
50
51 // the virtual COM port only shows up when requesting devices with the raw device guid!
52 static GUID USB_RAW_GUID = { 0xA5DCBF10, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } };
53 #elif defined(HAVE_LIBUDEV)
54 #include <dirent.h>
55 #include <poll.h>
56 extern "C" {
57 #include <libudev.h>
58 }
59 #elif defined(__FreeBSD__)
60 #include <stdio.h>
61 #include <unistd.h>
62 #endif
63
64 #define CEC_VID 0x2548
65 #define CEC_PID 0x1001
66
67 using namespace CEC;
68 using namespace std;
69
70 #if defined(HAVE_LIBUDEV)
71 bool TranslateComPort(CStdString &strString)
72 {
73 CStdString strTmp(strString);
74 strTmp.MakeReverse();
75 int iSlash = strTmp.Find('/');
76 if (iSlash >= 0)
77 {
78 strTmp = strTmp.Left(iSlash);
79 strTmp.MakeReverse();
80 strString.Format("%s/%s:1.0/tty", strString.c_str(), strTmp.c_str());
81 return true;
82 }
83
84 return false;
85 }
86
87 bool FindComPort(CStdString &strLocation)
88 {
89 CStdString strPort = strLocation;
90 bool bReturn(!strPort.IsEmpty());
91 CStdString strConfigLocation(strLocation);
92 if (TranslateComPort(strConfigLocation))
93 {
94 DIR *dir;
95 struct dirent *dirent;
96 if((dir = opendir(strConfigLocation.c_str())) == NULL)
97 return bReturn;
98
99 while ((dirent = readdir(dir)) != NULL)
100 {
101 if(strcmp((char*)dirent->d_name, "." ) != 0 && strcmp((char*)dirent->d_name, ".." ) != 0)
102 {
103 strPort.Format("/dev/%s", dirent->d_name);
104 if (!strPort.IsEmpty())
105 {
106 strLocation = strPort;
107 bReturn = true;
108 break;
109 }
110 }
111 }
112 closedir(dir);
113 }
114
115 return bReturn;
116 }
117 #endif
118
119 bool CUSBCECAdapterDetection::CanAutodetect(void)
120 {
121 #if defined(__APPLE__) || defined(HAVE_LIBUDEV) || defined(__WINDOWS__) || defined(__FreeBSD__)
122 return true;
123 #else
124 return false;
125 #endif
126 }
127
128 uint8_t CUSBCECAdapterDetection::FindAdapters(cec_adapter *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
129 {
130 uint8_t iFound(0);
131
132 #if defined(__APPLE__)
133 kern_return_t kresult;
134 char bsdPath[MAXPATHLEN] = {0};
135 io_iterator_t serialPortIterator;
136
137 CFMutableDictionaryRef classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
138 if (classesToMatch)
139 {
140 CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType));
141 kresult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, &serialPortIterator);
142 if (kresult == KERN_SUCCESS)
143 {
144 io_object_t serialService;
145 while ((serialService = IOIteratorNext(serialPortIterator)))
146 {
147 int iVendor = 0, iProduct = 0;
148 CFTypeRef bsdPathAsCFString;
149
150 // fetch the device path.
151 bsdPathAsCFString = IORegistryEntryCreateCFProperty(serialService,
152 CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
153 if (bsdPathAsCFString)
154 {
155 // convert the path from a CFString to a C (NUL-terminated) string.
156 CFStringGetCString((CFStringRef)bsdPathAsCFString, bsdPath, MAXPATHLEN - 1, kCFStringEncodingUTF8);
157 CFRelease(bsdPathAsCFString);
158
159 // now walk up the hierarchy until we find the entry with vendor/product IDs
160 io_registry_entry_t parent;
161 CFTypeRef vendorIdAsCFNumber = NULL;
162 CFTypeRef productIdAsCFNumber = NULL;
163 kern_return_t kresult = IORegistryEntryGetParentEntry(serialService, kIOServicePlane, &parent);
164 while (kresult == KERN_SUCCESS)
165 {
166 vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
167 kIOServicePlane, CFSTR(kUSBVendorID), kCFAllocatorDefault, 0);
168 productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
169 kIOServicePlane, CFSTR(kUSBProductID), kCFAllocatorDefault, 0);
170 if (vendorIdAsCFNumber && productIdAsCFNumber)
171 {
172 CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberIntType, &iVendor);
173 CFRelease(vendorIdAsCFNumber);
174 CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberIntType, &iProduct);
175 CFRelease(productIdAsCFNumber);
176 IOObjectRelease(parent);
177 break;
178 }
179 io_registry_entry_t oldparent = parent;
180 kresult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent);
181 IOObjectRelease(oldparent);
182 }
183 if (strlen(bsdPath) && iVendor == CEC_VID && iProduct == CEC_PID)
184 {
185 if (!strDevicePath || !strcmp(bsdPath, strDevicePath))
186 {
187 // on darwin, the device path is the same as the comm path.
188 snprintf(deviceList[iFound ].path, sizeof(deviceList[iFound].path), "%s", bsdPath);
189 snprintf(deviceList[iFound++].comm, sizeof(deviceList[iFound].path), "%s", bsdPath);
190 }
191 }
192 }
193 IOObjectRelease(serialService);
194 }
195 }
196 IOObjectRelease(serialPortIterator);
197 }
198 #elif defined(HAVE_LIBUDEV)
199 struct udev *udev;
200 if (!(udev = udev_new()))
201 return -1;
202
203 struct udev_enumerate *enumerate;
204 struct udev_list_entry *devices, *dev_list_entry;
205 struct udev_device *dev, *pdev;
206 enumerate = udev_enumerate_new(udev);
207 udev_enumerate_scan_devices(enumerate);
208 devices = udev_enumerate_get_list_entry(enumerate);
209 udev_list_entry_foreach(dev_list_entry, devices)
210 {
211 const char *strPath;
212 strPath = udev_list_entry_get_name(dev_list_entry);
213
214 dev = udev_device_new_from_syspath(udev, strPath);
215 if (!dev)
216 continue;
217
218 pdev = udev_device_get_parent(udev_device_get_parent(dev));
219 if (!pdev || !udev_device_get_sysattr_value(pdev,"idVendor") || !udev_device_get_sysattr_value(pdev, "idProduct"))
220 {
221 udev_device_unref(dev);
222 continue;
223 }
224
225 int iVendor, iProduct;
226 sscanf(udev_device_get_sysattr_value(pdev, "idVendor"), "%x", &iVendor);
227 sscanf(udev_device_get_sysattr_value(pdev, "idProduct"), "%x", &iProduct);
228 if (iVendor == CEC_VID && iProduct == CEC_PID)
229 {
230 CStdString strPath(udev_device_get_syspath(pdev));
231 if (!strDevicePath || !strcmp(strPath.c_str(), strDevicePath))
232 {
233 CStdString strComm(strPath);
234 if (FindComPort(strComm))
235 {
236 snprintf(deviceList[iFound ].path, sizeof(deviceList[iFound].path), "%s", strPath.c_str());
237 snprintf(deviceList[iFound++].comm, sizeof(deviceList[iFound].path), "%s", strComm.c_str());
238 }
239 }
240 }
241 udev_device_unref(dev);
242 }
243
244 udev_enumerate_unref(enumerate);
245 udev_unref(udev);
246 #elif defined(__WINDOWS__)
247 HDEVINFO hDevHandle;
248 DWORD required = 0, iMemberIndex = 0;
249 int nBufferSize = 0;
250
251 SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
252 deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
253
254 SP_DEVINFO_DATA devInfoData;
255 devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
256
257 if ((hDevHandle = SetupDiGetClassDevs(&USB_RAW_GUID, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)) == INVALID_HANDLE_VALUE)
258 return iFound;
259
260 BOOL bResult = true;
261 TCHAR *buffer = NULL;
262 PSP_DEVICE_INTERFACE_DETAIL_DATA devicedetailData;
263 while(bResult && iFound < iBufSize)
264 {
265 bResult = SetupDiEnumDeviceInfo(hDevHandle, iMemberIndex, &devInfoData);
266
267 if (bResult)
268 bResult = SetupDiEnumDeviceInterfaces(hDevHandle, 0, &USB_RAW_GUID, iMemberIndex, &deviceInterfaceData);
269
270 if(!bResult)
271 {
272 SetupDiDestroyDeviceInfoList(hDevHandle);
273 delete []buffer;
274 buffer = NULL;
275 return iFound;
276 }
277
278 iMemberIndex++;
279 BOOL bDetailResult = false;
280 {
281 // As per MSDN, Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with a
282 // NULL DeviceInterfaceDetailData pointer, a DeviceInterfaceDetailDataSize of zero,
283 // and a valid RequiredSize variable. In response to such a call, this function returns
284 // the required buffer size at RequiredSize and fails with GetLastError returning
285 // ERROR_INSUFFICIENT_BUFFER.
286 // Allocate an appropriately sized buffer and call the function again to get the interface details.
287
288 SetupDiGetDeviceInterfaceDetail(hDevHandle, &deviceInterfaceData, NULL, 0, &required, NULL);
289
290 buffer = new TCHAR[required];
291 devicedetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) buffer;
292 devicedetailData->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
293 nBufferSize = required;
294 }
295
296 bDetailResult = SetupDiGetDeviceInterfaceDetail(hDevHandle, &deviceInterfaceData, devicedetailData, nBufferSize , &required, NULL);
297 if(!bDetailResult)
298 continue;
299
300 if (strDevicePath && strcmp(strDevicePath, devicedetailData->DevicePath) != 0)
301 continue;
302
303 CStdString strVendorId;
304 CStdString strProductId;
305 CStdString strTmp(devicedetailData->DevicePath);
306 strVendorId.assign(strTmp.substr(strTmp.Find("vid_") + 4, 4));
307 strProductId.assign(strTmp.substr(strTmp.Find("pid_") + 4, 4));
308 if (strTmp.Find("&mi_") >= 0 && strTmp.Find("&mi_00") < 0)
309 continue;
310
311 int iVendor, iProduct;
312 sscanf(strVendorId, "%x", &iVendor);
313 sscanf(strProductId, "%x", &iProduct);
314 if (iVendor != CEC_VID || iProduct != CEC_PID)
315 continue;
316
317 HKEY hDeviceKey = SetupDiOpenDevRegKey(hDevHandle, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
318 if (!hDeviceKey)
319 continue;
320
321 TCHAR strPortName[256];
322 strPortName[0] = _T('\0');
323 DWORD dwSize = sizeof(strPortName);
324 DWORD dwType = 0;
325
326 /* search the registry */
327 if ((RegQueryValueEx(hDeviceKey, _T("PortName"), NULL, &dwType, reinterpret_cast<LPBYTE>(strPortName), &dwSize) == ERROR_SUCCESS) && (dwType == REG_SZ))
328 {
329 if (_tcslen(strPortName) > 3 && _tcsnicmp(strPortName, _T("COM"), 3) == 0 &&
330 _ttoi(&(strPortName[3])) > 0)
331 {
332 snprintf(deviceList[iFound ].path, sizeof(deviceList[iFound].path), "%s", devicedetailData->DevicePath);
333 snprintf(deviceList[iFound++].comm, sizeof(deviceList[iFound].path), "%s", strPortName);
334 }
335 }
336
337 RegCloseKey(hDeviceKey);
338 }
339 #elif defined(__FreeBSD__)
340 char devicePath[PATH_MAX + 1];
341 int i;
342
343 for (i = 0; i < 8; ++i)
344 {
345 (void)snprintf(devicePath, sizeof(devicePath), "/dev/ttyU%d", i);
346 if (!access(devicePath, 0))
347 {
348 snprintf(deviceList[iFound ].path, sizeof(deviceList[iFound].path), "%s", devicePath);
349 snprintf(deviceList[iFound++].comm, sizeof(deviceList[iFound].path), "%s", devicePath);
350 }
351 }
352 #else
353 //silence "unused" warnings
354 void *tmp = (void*)deviceList;
355 tmp = (void *)strDevicePath;
356 #endif
357
358 iBufSize = 0; /* silence "unused" warning on linux/osx */
359
360 return iFound;
361 }