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