-
Notifications
You must be signed in to change notification settings - Fork 2
/
USBController.cpp
429 lines (393 loc) · 13.2 KB
/
USBController.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
#include "stdafx.h"
#include "USBController.h"
//#include <objbase.h>
//#include <initguid.h>
#include <Setupapi.h>
#include <ntddser.h>
#pragma comment(lib, "Setupapi.lib")
HANDLE CUSBController::OpenComPort(const CString& PortSpecifier)
{
HANDLE hPort = CreateFile(PortSpecifier, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hPort == INVALID_HANDLE_VALUE)
return INVALID_HANDLE_VALUE;
PurgeComm(hPort, PURGE_RXCLEAR);
DCB dcb = { 0 };
if (!GetCommState(hPort, &dcb))
{
CloseHandle(hPort);
return INVALID_HANDLE_VALUE;
}
dcb.BaudRate = CBR_9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
if (!SetCommState(hPort, &dcb))
{
CloseHandle(hPort);
return INVALID_HANDLE_VALUE;
}
SetCommMask(hPort, EV_RXCHAR | EV_ERR); //receive character event
// Read this carefully because timeouts are important
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts
COMMTIMEOUTS timeouts = { 0 };
return hPort;
}
void CUSBController::CloseComPort(HANDLE hPort)
{
PurgeComm(hPort, PURGE_RXCLEAR);
CloseHandle(hPort);
}
bool CUSBController::IsComPortOpened(HANDLE hPort)
{
return hPort != INVALID_HANDLE_VALUE;
}
int CUSBController::ReadByte(HANDLE hPort)
{
int retVal;
BYTE Byte;
DWORD dwBytesTransferred;
if (FALSE == ReadFile(hPort, &Byte, 1, &dwBytesTransferred, 0)) //read 1
retVal = 0x101;
retVal = Byte;
return retVal;
}
/*************************************************************************
* Serial port enumeration routines
*
* The EnumSerialPort function will populate an array of SSerInfo structs,
* each of which contains information about one serial port present in
* the system. Note that this code must be linked with setupapi.lib,
* which is included with the Win32 SDK.
*
* by Zach Gorman <[email protected]>
*
* Copyright (c) 2002 Archetype Auction Software, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following condition is
* met: Redistributions of source code must retain the above copyright
* notice, this condition and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ARCHETYPE AUCTION SOFTWARE OR ITS
* AFFILIATES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************/
// The following define is from ntddser.h in the DDK. It is also
// needed for serial port enumeration.
/*#ifndef GUID_CLASS_COMPORT
DEFINE_GUID(GUID_CLASS_COMPORT, 0x86e0d1e0L, 0x8089, 0x11d0, 0x9c, 0xe4, \
0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73);
#endif*/
//---------------------------------------------------------------
// Routine for enumerating the available serial ports.
// Throws a CString on failure, describing the error that
// occurred. If bIgnoreBusyPorts is TRUE, ports that can't
// be opened for read/write access are not included.
void CUSBController::EnumSerialPorts(CArray<SSerInfo, SSerInfo&>& asi, BOOL bIgnoreBusyPorts)
{
// Clear the output array
asi.RemoveAll();
// Use different techniques to enumerate the available serial
// ports, depending on the OS we're using
OSVERSIONINFO vi;
vi.dwOSVersionInfoSize = sizeof(vi);
if (!::GetVersionEx(&vi)) {
CString str;
str.Format("Could not get OS version. (err=%lx)",
GetLastError());
throw str;
}
// Handle windows 9x and NT4 specially
if (vi.dwMajorVersion < 5) {
if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT)
EnumPortsWNt4(asi);
else
EnumPortsW9x(asi);
}
else {
// Win2k and later support a standard API for
// enumerating hardware devices.
EnumPortsWdm(asi);
}
for (int ii = 0; ii < asi.GetSize(); ii++)
{
SSerInfo& rsi = asi[ii];
if (bIgnoreBusyPorts) {
// Only display ports that can be opened for read/write
HANDLE hCom = CreateFile(rsi.strDevPath,
GENERIC_READ | GENERIC_WRITE,
0, /* comm devices must be opened w/exclusive-access */
NULL, /* no security attrs */
OPEN_EXISTING, /* comm devices must use OPEN_EXISTING */
0, /* not overlapped I/O */
NULL /* hTemplate must be NULL for comm devices */
);
if (hCom == INVALID_HANDLE_VALUE) {
// It can't be opened; remove it.
asi.RemoveAt(ii);
ii--;
continue;
}
else {
// It can be opened! Close it and add it to the list
::CloseHandle(hCom);
}
}
// Come up with a name for the device.
// If there is no friendly name, use the port name.
if (rsi.strFriendlyName.IsEmpty())
rsi.strFriendlyName = rsi.strPortName;
// If there is no description, try to make one up from
// the friendly name.
if (rsi.strPortDesc.IsEmpty()) {
// If the port name is of the form "ACME Port (COM3)"
// then strip off the " (COM3)"
rsi.strPortDesc = rsi.strFriendlyName;
int startdex = rsi.strPortDesc.Find(" (");
int enddex = rsi.strPortDesc.Find(")");
if (startdex > 0 && enddex ==
(rsi.strPortDesc.GetLength() - 1))
rsi.strPortDesc = rsi.strPortDesc.Left(startdex);
}
}
}
// Helpers for EnumSerialPorts
void CUSBController::EnumPortsWdm(CArray<SSerInfo, SSerInfo&>& asi)
{
CString strErr;
// Create a device information set that will be the container for
// the device interfaces.
GUID* guidDev = (GUID*)&GUID_CLASS_COMPORT;
HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
SP_DEVICE_INTERFACE_DETAIL_DATA* pDetData = NULL;
try {
hDevInfo = SetupDiGetClassDevs(guidDev,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
strErr.Format("SetupDiGetClassDevs failed. (err=%lx)",
GetLastError());
throw strErr;
}
// Enumerate the serial ports
BOOL bOk = TRUE;
SP_DEVICE_INTERFACE_DATA ifcData;
DWORD dwDetDataSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + 256;
pDetData = (SP_DEVICE_INTERFACE_DETAIL_DATA*) new char[dwDetDataSize];
// This is required, according to the documentation. Yes,
// it's weird.
ifcData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
pDetData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
for (DWORD ii = 0; bOk; ii++) {
bOk = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guidDev, ii, &ifcData);
if (bOk) {
// Got a device. Get the details.
SP_DEVINFO_DATA devdata = { sizeof(SP_DEVINFO_DATA) };
bOk = SetupDiGetDeviceInterfaceDetail(hDevInfo, &ifcData, pDetData, dwDetDataSize, NULL, &devdata);
if (bOk) {
CString strDevPath(pDetData->DevicePath);
// Got a path to the device. Try to get some more info.
CHAR fname[256];
CHAR desc[256];
BOOL bSuccess = SetupDiGetDeviceRegistryProperty(hDevInfo, &devdata, SPDRP_FRIENDLYNAME, NULL, (PBYTE)fname, sizeof(fname), NULL);
bSuccess = bSuccess && SetupDiGetDeviceRegistryProperty(hDevInfo, &devdata, SPDRP_DEVICEDESC, NULL, (PBYTE)desc, sizeof(desc), NULL);
BOOL bUsbDevice = FALSE;
CHAR locinfo[256];
if (SetupDiGetDeviceRegistryProperty(hDevInfo, &devdata, SPDRP_LOCATION_INFORMATION, NULL, (PBYTE)locinfo, sizeof(locinfo), NULL))
{
// Just check the first three characters to determine
// if the port is connected to the USB bus. This isn't
// an infallible method; it would be better to use the
// BUS GUID. Currently, Windows doesn't let you query
// that though (SPDRP_BUSTYPEGUID seems to exist in
// documentation only).
bUsbDevice = (strncmp(locinfo, "USB", 3) == 0);
}
if (bSuccess) {
// Add an entry to the array
SSerInfo si;
si.strDevPath = strDevPath;
si.strFriendlyName = fname;
si.strPortDesc = desc;
si.bUsbDevice = bUsbDevice;
asi.Add(si);
}
}
else {
strErr.Format("SetupDiGetDeviceInterfaceDetail failed. (err=%lx)",
GetLastError());
throw strErr;
}
}
else {
DWORD err = GetLastError();
if (err != ERROR_NO_MORE_ITEMS) {
strErr.Format("SetupDiEnumDeviceInterfaces failed. (err=%lx)", err);
throw strErr;
}
}
}
}
catch (CString strCatchErr) {
strErr = strCatchErr;
}
if (pDetData != NULL)
delete[](char*)pDetData;
if (hDevInfo != INVALID_HANDLE_VALUE)
SetupDiDestroyDeviceInfoList(hDevInfo);
if (!strErr.IsEmpty())
throw strErr;
}
void CUSBController::EnumPortsWNt4(CArray<SSerInfo, SSerInfo&>& asi)
{
// NT4's driver model is totally different, and not that
// many people use NT4 anymore. Just try all the COM ports
// between 1 and 16
SSerInfo si;
for (int ii = 1; ii <= 16; ii++) {
CString strPort;
strPort.Format("COM%d", ii);
si.strDevPath = CString("\\\\.\\") + strPort;
si.strPortName = strPort;
asi.Add(si);
}
}
void CUSBController::EnumPortsW9x(CArray<SSerInfo, SSerInfo&>& asi)
{
// Look at all keys in HKLM\Enum, searching for subkeys named
// *PNP0500 and *PNP0501. Within these subkeys, search for
// sub-subkeys containing value entries with the name "PORTNAME"
// Search all subkeys of HKLM\Enum\USBPORTS for PORTNAME entries.
// First, open HKLM\Enum
HKEY hkEnum = NULL;
HKEY hkSubEnum = NULL;
HKEY hkSubSubEnum = NULL;
try {
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Enum", 0, KEY_READ,
&hkEnum) != ERROR_SUCCESS)
throw CString("Could not read from HKLM\\Enum");
// Enumerate the subkeys of HKLM\Enum
char acSubEnum[128];
DWORD dwSubEnumIndex = 0;
DWORD dwSize = sizeof(acSubEnum);
while (RegEnumKeyEx(hkEnum, dwSubEnumIndex++, acSubEnum, &dwSize,
NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
HKEY hkSubEnum = NULL;
if (RegOpenKeyEx(hkEnum, acSubEnum, 0, KEY_READ,
&hkSubEnum) != ERROR_SUCCESS)
throw CString("Could not read from HKLM\\Enum\\") + acSubEnum;
// Enumerate the subkeys of HKLM\Enum\*\, looking for keys
// named *PNP0500 and *PNP0501 (or anything in USBPORTS)
BOOL bUsbDevice = (strcmp(acSubEnum, "USBPORTS") == 0);
char acSubSubEnum[128];
dwSize = sizeof(acSubSubEnum); // set the buffer size
DWORD dwSubSubEnumIndex = 0;
while (RegEnumKeyEx(hkSubEnum, dwSubSubEnumIndex++, acSubSubEnum,
&dwSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
BOOL bMatch = (strcmp(acSubSubEnum, "*PNP0500") == 0 ||
strcmp(acSubSubEnum, "*PNP0501") == 0 ||
bUsbDevice);
if (bMatch) {
HKEY hkSubSubEnum = NULL;
if (RegOpenKeyEx(hkSubEnum, acSubSubEnum, 0, KEY_READ,
&hkSubSubEnum) != ERROR_SUCCESS)
throw CString("Could not read from HKLM\\Enum\\") +
acSubEnum + "\\" + acSubSubEnum;
SearchPnpKeyW9x(hkSubSubEnum, bUsbDevice, asi);
RegCloseKey(hkSubSubEnum);
hkSubSubEnum = NULL;
}
dwSize = sizeof(acSubSubEnum); // restore the buffer size
}
RegCloseKey(hkSubEnum);
hkSubEnum = NULL;
dwSize = sizeof(acSubEnum); // restore the buffer size
}
}
catch (CString strError) {
if (hkEnum != NULL)
RegCloseKey(hkEnum);
if (hkSubEnum != NULL)
RegCloseKey(hkSubEnum);
if (hkSubSubEnum != NULL)
RegCloseKey(hkSubSubEnum);
throw strError;
}
RegCloseKey(hkEnum);
}
void CUSBController::SearchPnpKeyW9x(HKEY hkPnp, BOOL bUsbDevice, CArray<SSerInfo, SSerInfo&>& asi)
{
// Enumerate the subkeys of the given PNP key, looking for values with
// the name "PORTNAME"
// First, open HKLM\Enum
HKEY hkSubPnp = NULL;
try {
// Enumerate the subkeys of HKLM\Enum\*\PNP050[01]
char acSubPnp[128];
DWORD dwSubPnpIndex = 0;
DWORD dwSize = sizeof(acSubPnp);
while (RegEnumKeyEx(hkPnp, dwSubPnpIndex++, acSubPnp, &dwSize,
NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
HKEY hkSubPnp = NULL;
if (RegOpenKeyEx(hkPnp, acSubPnp, 0, KEY_READ,
&hkSubPnp) != ERROR_SUCCESS)
throw CString("Could not read from HKLM\\Enum\\...\\")
+ acSubPnp;
// Look for the PORTNAME value
char acValue[128];
dwSize = sizeof(acValue);
if (RegQueryValueEx(hkSubPnp, "PORTNAME", NULL, NULL, (BYTE*)acValue,
&dwSize) == ERROR_SUCCESS)
{
CString strPortName(acValue);
// Got the portname value. Look for a friendly name.
CString strFriendlyName;
dwSize = sizeof(acValue);
if (RegQueryValueEx(hkSubPnp, "FRIENDLYNAME", NULL, NULL, (BYTE*)acValue,
&dwSize) == ERROR_SUCCESS)
strFriendlyName = acValue;
// Prepare an entry for the output array.
SSerInfo si;
si.strDevPath = CString("\\\\.\\") + strPortName;
si.strPortName = strPortName;
si.strFriendlyName = strFriendlyName;
si.bUsbDevice = bUsbDevice;
// Overwrite duplicates.
BOOL bDup = FALSE;
for (int ii = 0; ii < asi.GetSize() && !bDup; ii++)
{
if (asi[ii].strPortName == strPortName) {
bDup = TRUE;
asi[ii] = si;
}
}
if (!bDup) {
// Add an entry to the array
asi.Add(si);
}
}
RegCloseKey(hkSubPnp);
hkSubPnp = NULL;
dwSize = sizeof(acSubPnp); // restore the buffer size
}
}
catch (CString strError) {
if (hkSubPnp != NULL)
RegCloseKey(hkSubPnp);
throw strError;
}
}