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/>.
21 #include "libserialport_internal.h"
23 /* USB path is a string of at most 8 decimal numbers < 128 separated by dots. */
24 #define MAX_USB_PATH ((8 * 3) + (7 * 1) + 1)
26 static void enumerate_hub(struct sp_port
*port
, const char *hub_name
,
27 const char *parent_path
, DEVINST dev_inst
);
29 static char *wc_to_utf8(PWCHAR wc_buffer
, ULONG wc_bytes
)
31 ULONG wc_length
= wc_bytes
/ sizeof(WCHAR
);
34 char *utf8_str
= NULL
;
36 /* Allocate aligned wide char buffer */
37 if (!(wc_str
= malloc((wc_length
+ 1) * sizeof(WCHAR
))))
40 /* Zero-terminate the wide char string. */
41 memcpy(wc_str
, wc_buffer
, wc_bytes
);
42 wc_str
[wc_length
] = 0;
44 /* Compute the size of the UTF-8 converted string. */
45 if (!(utf8_bytes
= WideCharToMultiByte(CP_ACP
, WC_NO_BEST_FIT_CHARS
, wc_str
, -1,
46 NULL
, 0, NULL
, NULL
)))
49 /* Allocate UTF-8 output buffer. */
50 if (!(utf8_str
= malloc(utf8_bytes
)))
53 /* Actually converted to UTF-8. */
54 if (!WideCharToMultiByte(CP_ACP
, WC_NO_BEST_FIT_CHARS
, wc_str
, -1,
55 utf8_str
, utf8_bytes
, NULL
, NULL
)) {
68 static char *get_root_hub_name(HANDLE host_controller
)
70 USB_ROOT_HUB_NAME root_hub_name
;
71 PUSB_ROOT_HUB_NAME root_hub_name_wc
;
72 char *root_hub_name_utf8
;
75 /* Compute the size of the root hub name string. */
76 if (!DeviceIoControl(host_controller
, IOCTL_USB_GET_ROOT_HUB_NAME
, 0, 0,
77 &root_hub_name
, sizeof(root_hub_name
), &size
, NULL
))
80 /* Allocate wide char root hub name string. */
81 size
= root_hub_name
.ActualLength
;
82 if (!(root_hub_name_wc
= malloc(size
)))
85 /* Actually get the root hub name string. */
86 if (!DeviceIoControl(host_controller
, IOCTL_USB_GET_ROOT_HUB_NAME
,
87 NULL
, 0, root_hub_name_wc
, size
, &size
, NULL
)) {
88 free(root_hub_name_wc
);
92 /* Convert the root hub name string to UTF-8. */
93 root_hub_name_utf8
= wc_to_utf8(root_hub_name_wc
->RootHubName
, size
- offsetof(USB_ROOT_HUB_NAME
, RootHubName
));
94 free(root_hub_name_wc
);
95 return root_hub_name_utf8
;
98 static char *get_external_hub_name(HANDLE hub
, ULONG connection_index
)
100 USB_NODE_CONNECTION_NAME ext_hub_name
;
101 PUSB_NODE_CONNECTION_NAME ext_hub_name_wc
;
102 char *ext_hub_name_utf8
;
105 /* Compute the size of the external hub name string. */
106 ext_hub_name
.ConnectionIndex
= connection_index
;
107 if (!DeviceIoControl(hub
, IOCTL_USB_GET_NODE_CONNECTION_NAME
,
108 &ext_hub_name
, sizeof(ext_hub_name
),
109 &ext_hub_name
, sizeof(ext_hub_name
), &size
, NULL
))
112 /* Allocate wide char external hub name string. */
113 size
= ext_hub_name
.ActualLength
;
114 if (size
<= sizeof(ext_hub_name
)
115 || !(ext_hub_name_wc
= malloc(size
)))
118 /* Get the name of the external hub attached to the specified port. */
119 ext_hub_name_wc
->ConnectionIndex
= connection_index
;
120 if (!DeviceIoControl(hub
, IOCTL_USB_GET_NODE_CONNECTION_NAME
,
121 ext_hub_name_wc
, size
,
122 ext_hub_name_wc
, size
, &size
, NULL
)) {
123 free(ext_hub_name_wc
);
127 /* Convert the external hub name string to UTF-8. */
128 ext_hub_name_utf8
= wc_to_utf8(ext_hub_name_wc
->NodeName
, size
- offsetof(USB_NODE_CONNECTION_NAME
, NodeName
));
129 free(ext_hub_name_wc
);
130 return ext_hub_name_utf8
;
133 static char *get_string_descriptor(HANDLE hub_device
, ULONG connection_index
,
134 UCHAR descriptor_index
)
136 char desc_req_buf
[sizeof(USB_DESCRIPTOR_REQUEST
) +
137 MAXIMUM_USB_STRING_LENGTH
] = { 0 };
138 PUSB_DESCRIPTOR_REQUEST desc_req
= (void *)desc_req_buf
;
139 PUSB_STRING_DESCRIPTOR desc
= (void *)(desc_req
+ 1);
140 ULONG size
= sizeof(desc_req_buf
);
142 desc_req
->ConnectionIndex
= connection_index
;
143 desc_req
->SetupPacket
.wValue
= (USB_STRING_DESCRIPTOR_TYPE
<< 8)
145 desc_req
->SetupPacket
.wIndex
= 0;
146 desc_req
->SetupPacket
.wLength
= (USHORT
) (size
- sizeof(*desc_req
));
148 if (!DeviceIoControl(hub_device
,
149 IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION
,
150 desc_req
, size
, desc_req
, size
, &size
, NULL
)
152 || desc
->bDescriptorType
!= USB_STRING_DESCRIPTOR_TYPE
153 || desc
->bLength
!= size
- sizeof(*desc_req
)
154 || desc
->bLength
% 2)
157 return wc_to_utf8(desc
->bString
, desc
->bLength
- offsetof(USB_STRING_DESCRIPTOR
, bString
));
160 static void enumerate_hub_ports(struct sp_port
*port
, HANDLE hub_device
,
161 ULONG nb_ports
, const char *parent_path
, DEVINST dev_inst
)
163 char path
[MAX_USB_PATH
];
166 for (index
= 1; index
<= nb_ports
; index
++) {
167 PUSB_NODE_CONNECTION_INFORMATION_EX connection_info_ex
;
168 ULONG size
= sizeof(*connection_info_ex
) + (30 * sizeof(USB_PIPE_INFO
));
170 if (!(connection_info_ex
= malloc(size
)))
173 connection_info_ex
->ConnectionIndex
= index
;
174 if (!DeviceIoControl(hub_device
,
175 IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX
,
176 connection_info_ex
, size
,
177 connection_info_ex
, size
, &size
, NULL
)) {
179 * Try to get CONNECTION_INFORMATION if
180 * CONNECTION_INFORMATION_EX did not work.
182 PUSB_NODE_CONNECTION_INFORMATION connection_info
;
184 size
= sizeof(*connection_info
) + (30 * sizeof(USB_PIPE_INFO
));
185 if (!(connection_info
= malloc(size
))) {
186 free(connection_info_ex
);
189 connection_info
->ConnectionIndex
= index
;
190 if (!DeviceIoControl(hub_device
,
191 IOCTL_USB_GET_NODE_CONNECTION_INFORMATION
,
192 connection_info
, size
,
193 connection_info
, size
, &size
, NULL
)) {
194 free(connection_info
);
195 free(connection_info_ex
);
199 connection_info_ex
->ConnectionIndex
= connection_info
->ConnectionIndex
;
200 connection_info_ex
->DeviceDescriptor
= connection_info
->DeviceDescriptor
;
201 connection_info_ex
->DeviceIsHub
= connection_info
->DeviceIsHub
;
202 connection_info_ex
->DeviceAddress
= connection_info
->DeviceAddress
;
203 free(connection_info
);
206 if (connection_info_ex
->DeviceIsHub
) {
207 /* Recursively enumerate external hub. */
209 if ((ext_hub_name
= get_external_hub_name(hub_device
, index
))) {
210 snprintf(path
, sizeof(path
), "%s%ld.",
211 parent_path
, connection_info_ex
->ConnectionIndex
);
212 enumerate_hub(port
, ext_hub_name
, path
, dev_inst
);
214 free(connection_info_ex
);
216 snprintf(path
, sizeof(path
), "%s%ld",
217 parent_path
, connection_info_ex
->ConnectionIndex
);
219 /* Check if this device is the one we search for. */
220 if (strcmp(path
, port
->usb_path
)) {
221 free(connection_info_ex
);
225 /* Finally grab detailed information regarding the device. */
226 port
->usb_address
= connection_info_ex
->DeviceAddress
+ 1;
227 port
->usb_vid
= connection_info_ex
->DeviceDescriptor
.idVendor
;
228 port
->usb_pid
= connection_info_ex
->DeviceDescriptor
.idProduct
;
230 if (connection_info_ex
->DeviceDescriptor
.iManufacturer
)
231 port
->usb_manufacturer
= get_string_descriptor(hub_device
, index
,
232 connection_info_ex
->DeviceDescriptor
.iManufacturer
);
233 if (connection_info_ex
->DeviceDescriptor
.iProduct
)
234 port
->usb_product
= get_string_descriptor(hub_device
, index
,
235 connection_info_ex
->DeviceDescriptor
.iProduct
);
236 if (connection_info_ex
->DeviceDescriptor
.iSerialNumber
) {
237 port
->usb_serial
= get_string_descriptor(hub_device
, index
,
238 connection_info_ex
->DeviceDescriptor
.iSerialNumber
);
239 if (port
->usb_serial
== NULL
) {
240 //composite device, get the parent's serial number
241 char device_id
[MAX_DEVICE_ID_LEN
];
242 if (CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
) {
243 if (CM_Get_Device_IDA(dev_inst
, device_id
, sizeof(device_id
), 0) == CR_SUCCESS
)
244 port
->usb_serial
= strdup(strrchr(device_id
, '\\')+1);
249 free(connection_info_ex
);
255 static void enumerate_hub(struct sp_port
*port
, const char *hub_name
,
256 const char *parent_path
, DEVINST dev_inst
)
258 USB_NODE_INFORMATION hub_info
;
260 ULONG size
= sizeof(hub_info
);
263 /* Open the hub with its full name. */
264 if (!(device_name
= malloc(strlen("\\\\.\\") + strlen(hub_name
) + 1)))
266 strcpy(device_name
, "\\\\.\\");
267 strcat(device_name
, hub_name
);
268 hub_device
= CreateFileA(device_name
, GENERIC_WRITE
, FILE_SHARE_WRITE
,
269 NULL
, OPEN_EXISTING
, 0, NULL
);
271 if (hub_device
== INVALID_HANDLE_VALUE
)
274 /* Get the number of ports of the hub. */
275 if (DeviceIoControl(hub_device
, IOCTL_USB_GET_NODE_INFORMATION
,
276 &hub_info
, size
, &hub_info
, size
, &size
, NULL
))
277 /* Enumerate the ports of the hub. */
278 enumerate_hub_ports(port
, hub_device
,
279 hub_info
.u
.HubInformation
.HubDescriptor
.bNumberOfPorts
, parent_path
, dev_inst
);
281 CloseHandle(hub_device
);
284 static void enumerate_host_controller(struct sp_port
*port
,
285 HANDLE host_controller_device
,
290 if ((root_hub_name
= get_root_hub_name(host_controller_device
))) {
291 enumerate_hub(port
, root_hub_name
, "", dev_inst
);
296 static void get_usb_details(struct sp_port
*port
, DEVINST dev_inst_match
)
298 HDEVINFO device_info
;
299 SP_DEVINFO_DATA device_info_data
;
302 device_info
= SetupDiGetClassDevs(&GUID_CLASS_USB_HOST_CONTROLLER
, NULL
, NULL
,
303 DIGCF_PRESENT
| DIGCF_DEVICEINTERFACE
);
304 device_info_data
.cbSize
= sizeof(device_info_data
);
306 for (i
= 0; SetupDiEnumDeviceInfo(device_info
, i
, &device_info_data
); i
++) {
307 SP_DEVICE_INTERFACE_DATA device_interface_data
;
308 PSP_DEVICE_INTERFACE_DETAIL_DATA device_detail_data
;
309 DEVINST dev_inst
= dev_inst_match
;
310 HANDLE host_controller_device
;
312 device_interface_data
.cbSize
= sizeof(device_interface_data
);
313 if (!SetupDiEnumDeviceInterfaces(device_info
, 0,
314 &GUID_CLASS_USB_HOST_CONTROLLER
,
315 i
, &device_interface_data
))
318 if (!SetupDiGetDeviceInterfaceDetail(device_info
,&device_interface_data
,
319 NULL
, 0, &size
, NULL
)
320 && GetLastError() != ERROR_INSUFFICIENT_BUFFER
)
323 if (!(device_detail_data
= malloc(size
)))
325 device_detail_data
->cbSize
= sizeof(*device_detail_data
);
326 if (!SetupDiGetDeviceInterfaceDetail(device_info
,&device_interface_data
,
327 device_detail_data
, size
, &size
,
329 free(device_detail_data
);
333 while (CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
334 && dev_inst
!= device_info_data
.DevInst
) { }
335 if (dev_inst
!= device_info_data
.DevInst
) {
336 free(device_detail_data
);
340 port
->usb_bus
= i
+ 1;
342 host_controller_device
= CreateFile(device_detail_data
->DevicePath
,
343 GENERIC_WRITE
, FILE_SHARE_WRITE
,
344 NULL
, OPEN_EXISTING
, 0, NULL
);
345 if (host_controller_device
!= INVALID_HANDLE_VALUE
) {
346 enumerate_host_controller(port
, host_controller_device
, dev_inst_match
);
347 CloseHandle(host_controller_device
);
349 free(device_detail_data
);
352 SetupDiDestroyDeviceInfoList(device_info
);
356 SP_PRIV
enum sp_return
get_port_details(struct sp_port
*port
)
359 * Description limited to 127 char, anything longer
360 * would not be user friendly anyway.
362 char description
[128];
363 SP_DEVINFO_DATA device_info_data
= { .cbSize
= sizeof(device_info_data
) };
364 HDEVINFO device_info
;
367 device_info
= SetupDiGetClassDevs(NULL
, 0, 0,
368 DIGCF_PRESENT
| DIGCF_ALLCLASSES
);
369 if (device_info
== INVALID_HANDLE_VALUE
)
370 RETURN_FAIL("SetupDiGetClassDevs() failed");
372 for (i
= 0; SetupDiEnumDeviceInfo(device_info
, i
, &device_info_data
); i
++) {
375 char value
[8], class[16];
379 /* Check if this is the device we are looking for. */
380 device_key
= SetupDiOpenDevRegKey(device_info
, &device_info_data
,
382 DIREG_DEV
, KEY_QUERY_VALUE
);
383 if (device_key
== INVALID_HANDLE_VALUE
)
385 size
= sizeof(value
);
386 if (RegQueryValueExA(device_key
, "PortName", NULL
, &type
, (LPBYTE
)value
,
387 &size
) != ERROR_SUCCESS
|| type
!= REG_SZ
) {
388 RegCloseKey(device_key
);
391 RegCloseKey(device_key
);
392 value
[sizeof(value
) - 1] = 0;
393 if (strcmp(value
, port
->name
))
396 /* Check port transport type. */
397 dev_inst
= device_info_data
.DevInst
;
398 size
= sizeof(class);
400 while (CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
&&
401 (cr
= CM_Get_DevNode_Registry_PropertyA(dev_inst
,
402 CM_DRP_CLASS
, 0, class, &size
, 0)) != CR_SUCCESS
) { }
403 if (cr
== CR_SUCCESS
) {
404 if (!strcmp(class, "USB"))
405 port
->transport
= SP_TRANSPORT_USB
;
408 /* Get port description (friendly name). */
409 dev_inst
= device_info_data
.DevInst
;
410 size
= sizeof(description
);
411 while ((cr
= CM_Get_DevNode_Registry_PropertyA(dev_inst
,
412 CM_DRP_FRIENDLYNAME
, 0, description
, &size
, 0)) != CR_SUCCESS
413 && CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
) { }
414 if (cr
== CR_SUCCESS
)
415 port
->description
= strdup(description
);
417 /* Get more informations for USB connected ports. */
418 if (port
->transport
== SP_TRANSPORT_USB
) {
419 char usb_path
[MAX_USB_PATH
] = "", tmp
[MAX_USB_PATH
];
420 char device_id
[MAX_DEVICE_ID_LEN
];
422 /* Recurse over parents to build the USB device path. */
423 dev_inst
= device_info_data
.DevInst
;
425 /* Verify that this layer of the tree is USB related. */
426 if (CM_Get_Device_IDA(dev_inst
, device_id
,
427 sizeof(device_id
), 0) != CR_SUCCESS
428 || strncmp(device_id
, "USB\\", 4))
431 /* Discard one layer for composite devices. */
432 char compat_ids
[512], *p
= compat_ids
;
433 size
= sizeof(compat_ids
);
434 if (CM_Get_DevNode_Registry_PropertyA(dev_inst
,
435 CM_DRP_COMPATIBLEIDS
, 0,
437 &size
, 0) == CR_SUCCESS
) {
439 if (!strncmp(p
, "USB\\COMPOSITE", 13))
447 /* Stop the recursion when reaching the USB root. */
448 if (!strncmp(device_id
, "USB\\ROOT", 8))
451 /* Prepend the address of current USB layer to the USB path. */
453 size
= sizeof(address
);
454 if (CM_Get_DevNode_Registry_PropertyA(dev_inst
, CM_DRP_ADDRESS
,
455 0, &address
, &size
, 0) == CR_SUCCESS
) {
456 strcpy(tmp
, usb_path
);
457 snprintf(usb_path
, sizeof(usb_path
), "%d%s%s",
458 (int)address
, *tmp
? "." : "", tmp
);
460 } while (CM_Get_Parent(&dev_inst
, dev_inst
, 0) == CR_SUCCESS
);
462 port
->usb_path
= strdup(usb_path
);
464 /* Wake up the USB device to be able to read string descriptor. */
465 char *escaped_port_name
;
467 if (!(escaped_port_name
= malloc(strlen(port
->name
) + 5)))
468 RETURN_ERROR(SP_ERR_MEM
, "Escaped port name malloc failed");
469 sprintf(escaped_port_name
, "\\\\.\\%s", port
->name
);
470 handle
= CreateFileA(escaped_port_name
, GENERIC_READ
, 0, 0,
472 FILE_ATTRIBUTE_NORMAL
|FILE_FLAG_OVERLAPPED
, 0);
473 free(escaped_port_name
);
476 /* Retrieve USB device details from the device descriptor. */
477 get_usb_details(port
, device_info_data
.DevInst
);
482 SetupDiDestroyDeviceInfoList(device_info
);
487 SP_PRIV
enum sp_return
list_ports(struct sp_port
***list
)
491 DWORD max_value_len
, max_data_size
, max_data_len
;
492 DWORD value_len
, data_size
, data_len
;
493 DWORD type
, index
= 0;
499 DEBUG("Opening registry key");
500 if ((result
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
, _T("HARDWARE\\DEVICEMAP\\SERIALCOMM"),
501 0, KEY_QUERY_VALUE
, &key
)) != ERROR_SUCCESS
) {
502 /* It's possible for this key to not exist if there are no serial ports
503 * at all. In that case we're done. Return a failure for any other error. */
504 if (result
!= ERROR_FILE_NOT_FOUND
) {
505 SetLastError(result
);
506 SET_FAIL(ret
, "RegOpenKeyEx() failed");
510 DEBUG("Querying registry key value and data sizes");
511 if ((result
= RegQueryInfoKey(key
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
512 &max_value_len
, &max_data_size
, NULL
, NULL
)) != ERROR_SUCCESS
) {
513 SetLastError(result
);
514 SET_FAIL(ret
, "RegQueryInfoKey() failed");
517 max_data_len
= max_data_size
/ sizeof(TCHAR
);
518 if (!(value
= malloc((max_value_len
+ 1) * sizeof(TCHAR
)))) {
519 SET_ERROR(ret
, SP_ERR_MEM
, "Registry value malloc failed");
522 if (!(data
= malloc((max_data_len
+ 1) * sizeof(TCHAR
)))) {
523 SET_ERROR(ret
, SP_ERR_MEM
, "Registry data malloc failed");
526 DEBUG("Iterating over values");
528 value_len
= max_value_len
+ 1,
529 data_size
= max_data_size
,
530 RegEnumValue(key
, index
, value
, &value_len
,
531 NULL
, &type
, (LPBYTE
)data
, &data_size
) == ERROR_SUCCESS
)
533 if (type
== REG_SZ
) {
534 data_len
= data_size
/ sizeof(TCHAR
);
535 data
[data_len
] = '\0';
537 name_len
= WideCharToMultiByte(CP_ACP
, 0, data
, -1, NULL
, 0, NULL
, NULL
);
539 name_len
= data_len
+ 1;
541 if (!(name
= malloc(name_len
))) {
542 SET_ERROR(ret
, SP_ERR_MEM
, "Registry port name malloc failed");
546 WideCharToMultiByte(CP_ACP
, 0, data
, -1, name
, name_len
, NULL
, NULL
);
550 DEBUG_FMT("Found port %s", name
);
551 if (!(*list
= list_append(*list
, name
))) {
552 SET_ERROR(ret
, SP_ERR_MEM
, "List append failed");