2 * This file is part of the libserialport project.
4 * Copyright (C) 2013-2014 Martin Ling <martin-libserialport@earth.li>
5 * Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "libserialport.h"
23 #include "libserialport_internal.h"
25 /* USB path is a string of at most 8 decimal numbers < 128 separated by dots. */
26 #define MAX_USB_PATH ((8 * 3) + (7 * 1) + 1)
28 static void enumerate_hub(struct sp_port
*port
, const char *hub_name
,
29 const char *parent_path
, DEVINST dev_inst
);
31 static char *wc_to_utf8(PWCHAR wc_buffer
, ULONG size
)
33 ULONG wc_length
= size
/ sizeof(WCHAR
);
34 WCHAR wc_str
[wc_length
+ 1];
37 /* Zero-terminate the wide char string. */
38 memcpy(wc_str
, wc_buffer
, size
);
39 wc_str
[wc_length
] = 0;
41 /* Compute the size of the UTF-8 converted string. */
42 if (!(size
= WideCharToMultiByte(CP_ACP
, WC_NO_BEST_FIT_CHARS
, wc_str
, -1,
43 NULL
, 0, NULL
, NULL
)))
46 /* Allocate UTF-8 output buffer. */
47 if (!(utf8_str
= malloc(size
)))
50 /* Actually converted to UTF-8. */
51 if (!WideCharToMultiByte(CP_ACP
, WC_NO_BEST_FIT_CHARS
, wc_str
, -1,
52 utf8_str
, size
, NULL
, NULL
)) {
60 static char *get_root_hub_name(HANDLE host_controller
)
62 USB_ROOT_HUB_NAME root_hub_name
;
63 PUSB_ROOT_HUB_NAME root_hub_name_wc
;
64 char *root_hub_name_utf8
;
67 /* Compute the size of the root hub name string. */
68 if (!DeviceIoControl(host_controller
, IOCTL_USB_GET_ROOT_HUB_NAME
, 0, 0,
69 &root_hub_name
, sizeof(root_hub_name
), &size
, NULL
))
72 /* Allocate wide char root hub name string. */
73 size
= root_hub_name
.ActualLength
;
74 if (!(root_hub_name_wc
= malloc(size
)))
77 /* Actually get the root hub name string. */
78 if (!DeviceIoControl(host_controller
, IOCTL_USB_GET_ROOT_HUB_NAME
,
79 NULL
, 0, root_hub_name_wc
, size
, &size
, NULL
)) {
80 free(root_hub_name_wc
);
84 /* Convert the root hub name string to UTF-8. */
85 root_hub_name_utf8
= wc_to_utf8(root_hub_name_wc
->RootHubName
, size
- offsetof(USB_ROOT_HUB_NAME
, RootHubName
));
86 free(root_hub_name_wc
);
87 return root_hub_name_utf8
;
90 static char *get_external_hub_name(HANDLE hub
, ULONG connection_index
)
92 USB_NODE_CONNECTION_NAME ext_hub_name
;
93 PUSB_NODE_CONNECTION_NAME ext_hub_name_wc
;
94 char *ext_hub_name_utf8
;
97 /* Compute the size of the external hub name string. */
98 ext_hub_name
.ConnectionIndex
= connection_index
;
99 if (!DeviceIoControl(hub
, IOCTL_USB_GET_NODE_CONNECTION_NAME
,
100 &ext_hub_name
, sizeof(ext_hub_name
),
101 &ext_hub_name
, sizeof(ext_hub_name
), &size
, NULL
))
104 /* Allocate wide char external hub name string. */
105 size
= ext_hub_name
.ActualLength
;
106 if (size
<= sizeof(ext_hub_name
)
107 || !(ext_hub_name_wc
= malloc(size
)))
110 /* Get the name of the external hub attached to the specified port. */
111 ext_hub_name_wc
->ConnectionIndex
= connection_index
;
112 if (!DeviceIoControl(hub
, IOCTL_USB_GET_NODE_CONNECTION_NAME
,
113 ext_hub_name_wc
, size
,
114 ext_hub_name_wc
, size
, &size
, NULL
)) {
115 free(ext_hub_name_wc
);
119 /* Convert the external hub name string to UTF-8. */
120 ext_hub_name_utf8
= wc_to_utf8(ext_hub_name_wc
->NodeName
, size
- offsetof(USB_NODE_CONNECTION_NAME
, NodeName
));
121 free(ext_hub_name_wc
);
122 return ext_hub_name_utf8
;
125 static char *get_string_descriptor(HANDLE hub_device
, ULONG connection_index
,
126 UCHAR descriptor_index
)
128 char desc_req_buf
[sizeof(USB_DESCRIPTOR_REQUEST
) +
129 MAXIMUM_USB_STRING_LENGTH
] = { 0 };
130 PUSB_DESCRIPTOR_REQUEST desc_req
= (void *)desc_req_buf
;
131 PUSB_STRING_DESCRIPTOR desc
= (void *)(desc_req
+ 1);
132 ULONG size
= sizeof(desc_req_buf
);
134 desc_req
->ConnectionIndex
= connection_index
;
135 desc_req
->SetupPacket
.wValue
= (USB_STRING_DESCRIPTOR_TYPE
<< 8)
137 desc_req
->SetupPacket
.wIndex
= 0;
138 desc_req
->SetupPacket
.wLength
= size
- sizeof(*desc_req
);
140 if (!DeviceIoControl(hub_device
,
141 IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION
,
142 desc_req
, size
, desc_req
, size
, &size
, NULL
)
144 || desc
->bDescriptorType
!= USB_STRING_DESCRIPTOR_TYPE
145 || desc
->bLength
!= size
- sizeof(*desc_req
)
146 || desc
->bLength
% 2)
149 return wc_to_utf8(desc
->bString
, desc
->bLength
- offsetof(USB_STRING_DESCRIPTOR
, bString
));
152 static void enumerate_hub_ports(struct sp_port
*port
, HANDLE hub_device
,
153 ULONG nb_ports
, const char *parent_path
, DEVINST dev_inst
)
155 char path
[MAX_USB_PATH
];
158 for (index
= 1; index
<= nb_ports
; index
++) {
159 PUSB_NODE_CONNECTION_INFORMATION_EX connection_info_ex
;
160 ULONG size
= sizeof(*connection_info_ex
) + (30 * sizeof(USB_PIPE_INFO
));
162 if (!(connection_info_ex
= malloc(size
)))
165 connection_info_ex
->ConnectionIndex
= index
;
166 if (!DeviceIoControl(hub_device
,
167 IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX
,
168 connection_info_ex
, size
,
169 connection_info_ex
, size
, &size
, NULL
)) {
171 * Try to get CONNECTION_INFORMATION if
172 * CONNECTION_INFORMATION_EX did not work.
174 PUSB_NODE_CONNECTION_INFORMATION connection_info
;
176 size
= sizeof(*connection_info
) + (30 * sizeof(USB_PIPE_INFO
));
177 if (!(connection_info
= malloc(size
))) {
178 free(connection_info_ex
);
181 connection_info
->ConnectionIndex
= index
;
182 if (!DeviceIoControl(hub_device
,
183 IOCTL_USB_GET_NODE_CONNECTION_INFORMATION
,
184 connection_info
, size
,
185 connection_info
, size
, &size
, NULL
)) {
186 free(connection_info
);
187 free(connection_info_ex
);
191 connection_info_ex
->ConnectionIndex
= connection_info
->ConnectionIndex
;
192 connection_info_ex
->DeviceDescriptor
= connection_info
->DeviceDescriptor
;
193 connection_info_ex
->DeviceIsHub
= connection_info
->DeviceIsHub
;
194 connection_info_ex
->DeviceAddress
= connection_info
->DeviceAddress
;
195 free(connection_info
);
198 if (connection_info_ex
->DeviceIsHub
) {
199 /* Recursively enumerate external hub. */
201 if ((ext_hub_name
= get_external_hub_name(hub_device
, index
))) {
202 snprintf(path
, sizeof(path
), "%s%ld.",
203 parent_path
, connection_info_ex
->ConnectionIndex
);
204 enumerate_hub(port
, ext_hub_name
, path
, dev_inst
);
206 free(connection_info_ex
);
208 snprintf(path
, sizeof(path
), "%s%ld",
209 parent_path
, connection_info_ex
->ConnectionIndex
);
211 /* Check if this device is the one we search for. */
212 if (strcmp(path
, port
->usb_path
)) {
213 free(connection_info_ex
);
217 /* Finally grab detailed information regarding the device. */
218 port
->usb_address
= connection_info_ex
->DeviceAddress
+ 1;
219 port
->usb_vid
= connection_info_ex
->DeviceDescriptor
.idVendor
;
220 port
->usb_pid
= connection_info_ex
->DeviceDescriptor
.idProduct
;
222 if (connection_info_ex
->DeviceDescriptor
.iManufacturer
)
223 port
->usb_manufacturer
= get_string_descriptor(hub_device
,index
,
224 connection_info_ex
->DeviceDescriptor
.iManufacturer
);
225 if (connection_info_ex
->DeviceDescriptor
.iProduct
)
226 port
->usb_product
= get_string_descriptor(hub_device
, index
,
227 connection_info_ex
->DeviceDescriptor
.iProduct
);
228 if (connection_info_ex
->DeviceDescriptor
.iSerialNumber
) {
229 port
->usb_serial
= get_string_descriptor(hub_device
, index
,
230 connection_info_ex
->DeviceDescriptor
.iSerialNumber
);
231 if (port
->usb_serial
== NULL
) {
232 //composite device, get the parent's serial number
233 char device_id
[MAX_DEVICE_ID_LEN
];
234 if (CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
) {
235 if (CM_Get_Device_IDA(dev_inst
, device_id
, sizeof(device_id
), 0) == CR_SUCCESS
)
236 port
->usb_serial
= strdup(strrchr(device_id
, '\\')+1);
241 free(connection_info_ex
);
247 static void enumerate_hub(struct sp_port
*port
, const char *hub_name
,
248 const char *parent_path
, DEVINST dev_inst
)
250 USB_NODE_INFORMATION hub_info
;
252 ULONG size
= sizeof(hub_info
);
255 /* Open the hub with its full name. */
256 if (!(device_name
= malloc(strlen("\\\\.\\") + strlen(hub_name
) + 1)))
258 strcpy(device_name
, "\\\\.\\");
259 strcat(device_name
, hub_name
);
260 hub_device
= CreateFile(device_name
, GENERIC_WRITE
, FILE_SHARE_WRITE
,
261 NULL
, OPEN_EXISTING
, 0, NULL
);
263 if (hub_device
== INVALID_HANDLE_VALUE
)
266 /* Get the number of ports of the hub. */
267 if (DeviceIoControl(hub_device
, IOCTL_USB_GET_NODE_INFORMATION
,
268 &hub_info
, size
, &hub_info
, size
, &size
, NULL
))
269 /* Enumerate the ports of the hub. */
270 enumerate_hub_ports(port
, hub_device
,
271 hub_info
.u
.HubInformation
.HubDescriptor
.bNumberOfPorts
, parent_path
, dev_inst
);
273 CloseHandle(hub_device
);
276 static void enumerate_host_controller(struct sp_port
*port
,
277 HANDLE host_controller_device
,
282 if ((root_hub_name
= get_root_hub_name(host_controller_device
))) {
283 enumerate_hub(port
, root_hub_name
, "", dev_inst
);
288 static void get_usb_details(struct sp_port
*port
, DEVINST dev_inst_match
)
290 HDEVINFO device_info
;
291 SP_DEVINFO_DATA device_info_data
;
294 device_info
= SetupDiGetClassDevs(&GUID_CLASS_USB_HOST_CONTROLLER
, NULL
, NULL
,
295 DIGCF_PRESENT
| DIGCF_DEVICEINTERFACE
);
296 device_info_data
.cbSize
= sizeof(device_info_data
);
298 for (i
= 0; SetupDiEnumDeviceInfo(device_info
, i
, &device_info_data
); i
++) {
299 SP_DEVICE_INTERFACE_DATA device_interface_data
;
300 PSP_DEVICE_INTERFACE_DETAIL_DATA device_detail_data
;
301 DEVINST dev_inst
= dev_inst_match
;
302 HANDLE host_controller_device
;
304 device_interface_data
.cbSize
= sizeof(device_interface_data
);
305 if (!SetupDiEnumDeviceInterfaces(device_info
, 0,
306 &GUID_CLASS_USB_HOST_CONTROLLER
,
307 i
, &device_interface_data
))
310 if (!SetupDiGetDeviceInterfaceDetail(device_info
,&device_interface_data
,
311 NULL
, 0, &size
, NULL
)
312 && GetLastError() != ERROR_INSUFFICIENT_BUFFER
)
315 if (!(device_detail_data
= malloc(size
)))
317 device_detail_data
->cbSize
= sizeof(*device_detail_data
);
318 if (!SetupDiGetDeviceInterfaceDetail(device_info
,&device_interface_data
,
319 device_detail_data
, size
, &size
,
321 free(device_detail_data
);
325 while (CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
326 && dev_inst
!= device_info_data
.DevInst
) { }
327 if (dev_inst
!= device_info_data
.DevInst
) {
328 free(device_detail_data
);
332 port
->usb_bus
= i
+ 1;
334 host_controller_device
= CreateFile(device_detail_data
->DevicePath
,
335 GENERIC_WRITE
, FILE_SHARE_WRITE
,
336 NULL
, OPEN_EXISTING
, 0, NULL
);
337 if (host_controller_device
!= INVALID_HANDLE_VALUE
) {
338 enumerate_host_controller(port
, host_controller_device
, dev_inst_match
);
339 CloseHandle(host_controller_device
);
341 free(device_detail_data
);
344 SetupDiDestroyDeviceInfoList(device_info
);
348 SP_PRIV
enum sp_return
get_port_details(struct sp_port
*port
)
351 * Description limited to 127 char, anything longer
352 * would not be user friendly anyway.
354 char description
[128];
355 SP_DEVINFO_DATA device_info_data
= { .cbSize
= sizeof(device_info_data
) };
356 HDEVINFO device_info
;
359 device_info
= SetupDiGetClassDevs(NULL
, 0, 0,
360 DIGCF_PRESENT
| DIGCF_ALLCLASSES
);
361 if (device_info
== INVALID_HANDLE_VALUE
)
362 RETURN_FAIL("SetupDiGetClassDevs() failed");
364 for (i
= 0; SetupDiEnumDeviceInfo(device_info
, i
, &device_info_data
); i
++) {
367 char value
[8], class[16];
371 /* Check if this is the device we are looking for. */
372 device_key
= SetupDiOpenDevRegKey(device_info
, &device_info_data
,
374 DIREG_DEV
, KEY_QUERY_VALUE
);
375 if (device_key
== INVALID_HANDLE_VALUE
)
377 size
= sizeof(value
);
378 if (RegQueryValueExA(device_key
, "PortName", NULL
, &type
, (LPBYTE
)value
,
379 &size
) != ERROR_SUCCESS
|| type
!= REG_SZ
) {
380 RegCloseKey(device_key
);
383 RegCloseKey(device_key
);
384 value
[sizeof(value
)-1] = 0;
385 if (strcmp(value
, port
->name
))
388 /* Check port transport type. */
389 dev_inst
= device_info_data
.DevInst
;
390 size
= sizeof(class);
392 while (CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
&&
393 (cr
= CM_Get_DevNode_Registry_PropertyA(dev_inst
,
394 CM_DRP_CLASS
, 0, class, &size
, 0)) != CR_SUCCESS
) { }
395 if (cr
== CR_SUCCESS
) {
396 if (!strcmp(class, "USB"))
397 port
->transport
= SP_TRANSPORT_USB
;
400 /* Get port description (friendly name). */
401 dev_inst
= device_info_data
.DevInst
;
402 size
= sizeof(description
);
403 while ((cr
= CM_Get_DevNode_Registry_PropertyA(dev_inst
,
404 CM_DRP_FRIENDLYNAME
, 0, description
, &size
, 0)) != CR_SUCCESS
405 && CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
) { }
406 if (cr
== CR_SUCCESS
)
407 port
->description
= strdup(description
);
409 /* Get more informations for USB connected ports. */
410 if (port
->transport
== SP_TRANSPORT_USB
) {
411 char usb_path
[MAX_USB_PATH
] = "", tmp
[MAX_USB_PATH
];
412 char device_id
[MAX_DEVICE_ID_LEN
];
414 /* Recurse over parents to build the USB device path. */
415 dev_inst
= device_info_data
.DevInst
;
417 /* Verify that this layer of the tree is USB related. */
418 if (CM_Get_Device_IDA(dev_inst
, device_id
,
419 sizeof(device_id
), 0) != CR_SUCCESS
420 || strncmp(device_id
, "USB\\", 4))
423 /* Discard one layer for composite devices. */
424 char compat_ids
[512], *p
= compat_ids
;
425 size
= sizeof(compat_ids
);
426 if (CM_Get_DevNode_Registry_PropertyA(dev_inst
,
427 CM_DRP_COMPATIBLEIDS
, 0,
429 &size
, 0) == CR_SUCCESS
) {
431 if (!strncmp(p
, "USB\\COMPOSITE", 13))
439 /* Stop the recursion when reaching the USB root. */
440 if (!strncmp(device_id
, "USB\\ROOT", 8))
443 /* Prepend the address of current USB layer to the USB path. */
445 size
= sizeof(address
);
446 if (CM_Get_DevNode_Registry_PropertyA(dev_inst
, CM_DRP_ADDRESS
,
447 0, &address
, &size
, 0) == CR_SUCCESS
) {
448 strcpy(tmp
, usb_path
);
449 snprintf(usb_path
, sizeof(usb_path
), "%d%s%s",
450 (int)address
, *tmp
? "." : "", tmp
);
452 } while (CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
);
454 port
->usb_path
= strdup(usb_path
);
456 /* Wake up the USB device to be able to read string descriptor. */
457 char *escaped_port_name
;
459 if (!(escaped_port_name
= malloc(strlen(port
->name
) + 5)))
460 RETURN_ERROR(SP_ERR_MEM
, "Escaped port name malloc failed");
461 sprintf(escaped_port_name
, "\\\\.\\%s", port
->name
);
462 handle
= CreateFile(escaped_port_name
, GENERIC_READ
, 0, 0,
464 FILE_ATTRIBUTE_NORMAL
|FILE_FLAG_OVERLAPPED
, 0);
465 free(escaped_port_name
);
468 /* Retrieve USB device details from the device descriptor. */
469 get_usb_details(port
, device_info_data
.DevInst
);
474 SetupDiDestroyDeviceInfoList(device_info
);
479 SP_PRIV
enum sp_return
list_ports(struct sp_port
***list
)
483 DWORD max_value_len
, max_data_size
, max_data_len
;
484 DWORD value_len
, data_size
, data_len
;
485 DWORD type
, index
= 0;
490 DEBUG("Opening registry key");
491 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE
, _T("HARDWARE\\DEVICEMAP\\SERIALCOMM"),
492 0, KEY_QUERY_VALUE
, &key
) != ERROR_SUCCESS
) {
493 SET_FAIL(ret
, "RegOpenKeyEx() failed");
496 DEBUG("Querying registry key value and data sizes");
497 if (RegQueryInfoKey(key
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
498 &max_value_len
, &max_data_size
, NULL
, NULL
) != ERROR_SUCCESS
) {
499 SET_FAIL(ret
, "RegQueryInfoKey() failed");
502 max_data_len
= max_data_size
/ sizeof(TCHAR
);
503 if (!(value
= malloc((max_value_len
+ 1) * sizeof(TCHAR
)))) {
504 SET_ERROR(ret
, SP_ERR_MEM
, "Registry value malloc failed");
507 if (!(data
= malloc((max_data_len
+ 1) * sizeof(TCHAR
)))) {
508 SET_ERROR(ret
, SP_ERR_MEM
, "Registry data malloc failed");
511 DEBUG("Iterating over values");
513 value_len
= max_value_len
+ 1,
514 data_size
= max_data_size
,
515 RegEnumValue(key
, index
, value
, &value_len
,
516 NULL
, &type
, (LPBYTE
)data
, &data_size
) == ERROR_SUCCESS
)
518 if (type
== REG_SZ
) {
519 data_len
= data_size
/ sizeof(TCHAR
);
520 data
[data_len
] = '\0';
522 name_len
= WideCharToMultiByte(CP_ACP
, 0, data
, -1, NULL
, 0, NULL
, NULL
);
524 name_len
= data_len
+ 1;
526 if (!(name
= malloc(name_len
))) {
527 SET_ERROR(ret
, SP_ERR_MEM
, "Registry port name malloc failed");
531 WideCharToMultiByte(CP_ACP
, 0, data
, -1, name
, name_len
, NULL
, NULL
);
535 DEBUG_FMT("Found port %s", name
);
536 if (!(*list
= list_append(*list
, name
))) {
537 SET_ERROR(ret
, SP_ERR_MEM
, "List append failed");