diff options
Diffstat (limited to 'software/vendor/go.bug.st/serial/enumerator')
8 files changed, 981 insertions, 0 deletions
diff --git a/software/vendor/go.bug.st/serial/enumerator/doc.go b/software/vendor/go.bug.st/serial/enumerator/doc.go new file mode 100644 index 0000000..a3f887d --- /dev/null +++ b/software/vendor/go.bug.st/serial/enumerator/doc.go @@ -0,0 +1,15 @@ +// +// Copyright 2014-2023 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +/* +Package enumerator is a golang cross-platform library for USB serial port discovery. + +This library has been tested on Linux, Windows and Mac and uses specific OS +services to enumerate USB PID/VID, in particular on MacOSX the use of cgo is +required in order to access the IOKit Framework. This means that the library +cannot be easily cross compiled for darwin/* targets. +*/ +package enumerator diff --git a/software/vendor/go.bug.st/serial/enumerator/enumerator.go b/software/vendor/go.bug.st/serial/enumerator/enumerator.go new file mode 100644 index 0000000..420f451 --- /dev/null +++ b/software/vendor/go.bug.st/serial/enumerator/enumerator.go @@ -0,0 +1,46 @@ +// +// Copyright 2014-2023 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +package enumerator + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output syscall_windows.go usb_windows.go + +// PortDetails contains detailed information about USB serial port. +// Use GetDetailedPortsList function to retrieve it. +type PortDetails struct { + Name string + IsUSB bool + VID string + PID string + SerialNumber string + + // Manufacturer string + + // Product is an OS-dependent string that describes the serial port, it may + // be not always available and it may be different across OS. + Product string +} + +// GetDetailedPortsList retrieve ports details like USB VID/PID. +// Please note that this function may not be available on all OS: +// in that case a FunctionNotImplemented error is returned. +func GetDetailedPortsList() ([]*PortDetails, error) { + return nativeGetDetailedPortsList() +} + +// PortEnumerationError is the error type for serial ports enumeration +type PortEnumerationError struct { + causedBy error +} + +// Error returns the complete error code with details on the cause of the error +func (e PortEnumerationError) Error() string { + reason := "Error while enumerating serial ports" + if e.causedBy != nil { + reason += ": " + e.causedBy.Error() + } + return reason +} diff --git a/software/vendor/go.bug.st/serial/enumerator/syscall_windows.go b/software/vendor/go.bug.st/serial/enumerator/syscall_windows.go new file mode 100644 index 0000000..2c6b379 --- /dev/null +++ b/software/vendor/go.bug.st/serial/enumerator/syscall_windows.go @@ -0,0 +1,167 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package enumerator + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modsetupapi = windows.NewLazySystemDLL("setupapi.dll") + modcfgmgr32 = windows.NewLazySystemDLL("cfgmgr32.dll") + + procSetupDiClassGuidsFromNameW = modsetupapi.NewProc("SetupDiClassGuidsFromNameW") + procSetupDiGetClassDevsW = modsetupapi.NewProc("SetupDiGetClassDevsW") + procSetupDiDestroyDeviceInfoList = modsetupapi.NewProc("SetupDiDestroyDeviceInfoList") + procSetupDiEnumDeviceInfo = modsetupapi.NewProc("SetupDiEnumDeviceInfo") + procSetupDiGetDeviceInstanceIdW = modsetupapi.NewProc("SetupDiGetDeviceInstanceIdW") + procSetupDiOpenDevRegKey = modsetupapi.NewProc("SetupDiOpenDevRegKey") + procSetupDiGetDeviceRegistryPropertyW = modsetupapi.NewProc("SetupDiGetDeviceRegistryPropertyW") + procCM_Get_Parent = modcfgmgr32.NewProc("CM_Get_Parent") + procCM_Get_Device_ID_Size = modcfgmgr32.NewProc("CM_Get_Device_ID_Size") + procCM_Get_Device_IDW = modcfgmgr32.NewProc("CM_Get_Device_IDW") + procCM_MapCrToWin32Err = modcfgmgr32.NewProc("CM_MapCrToWin32Err") +) + +func setupDiClassGuidsFromNameInternal(class string, guid *guid, guidSize uint32, requiredSize *uint32) (err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(class) + if err != nil { + return + } + return _setupDiClassGuidsFromNameInternal(_p0, guid, guidSize, requiredSize) +} + +func _setupDiClassGuidsFromNameInternal(class *uint16, guid *guid, guidSize uint32, requiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiClassGuidsFromNameW.Addr(), 4, uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(guid)), uintptr(guidSize), uintptr(unsafe.Pointer(requiredSize)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetClassDevs(guid *guid, enumerator *string, hwndParent uintptr, flags uint32) (set devicesSet, err error) { + r0, _, e1 := syscall.Syscall6(procSetupDiGetClassDevsW.Addr(), 4, uintptr(unsafe.Pointer(guid)), uintptr(unsafe.Pointer(enumerator)), uintptr(hwndParent), uintptr(flags), 0, 0) + set = devicesSet(r0) + if set == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiDestroyDeviceInfoList(set devicesSet) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiDestroyDeviceInfoList.Addr(), 1, uintptr(set), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiEnumDeviceInfo(set devicesSet, index uint32, info *devInfoData) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiEnumDeviceInfo.Addr(), 3, uintptr(set), uintptr(index), uintptr(unsafe.Pointer(info))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetDeviceInstanceId(set devicesSet, devInfo *devInfoData, devInstanceId unsafe.Pointer, devInstanceIdSize uint32, requiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiGetDeviceInstanceIdW.Addr(), 5, uintptr(set), uintptr(unsafe.Pointer(devInfo)), uintptr(devInstanceId), uintptr(devInstanceIdSize), uintptr(unsafe.Pointer(requiredSize)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiOpenDevRegKey(set devicesSet, devInfo *devInfoData, scope dicsScope, hwProfile uint32, keyType uint32, samDesired regsam) (hkey syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall6(procSetupDiOpenDevRegKey.Addr(), 6, uintptr(set), uintptr(unsafe.Pointer(devInfo)), uintptr(scope), uintptr(hwProfile), uintptr(keyType), uintptr(samDesired)) + hkey = syscall.Handle(r0) + if hkey == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, property deviceProperty, propertyType *uint32, outValue *byte, bufSize uint32, reqSize *uint32) (res bool) { + r0, _, _ := syscall.Syscall9(procSetupDiGetDeviceRegistryPropertyW.Addr(), 7, uintptr(set), uintptr(unsafe.Pointer(devInfo)), uintptr(property), uintptr(unsafe.Pointer(propertyType)), uintptr(unsafe.Pointer(outValue)), uintptr(bufSize), uintptr(unsafe.Pointer(reqSize)), 0, 0) + res = r0 != 0 + return +} + +func cmGetParent(outParentDev *devInstance, dev devInstance, flags uint32) (cmErr cmError) { + r0, _, _ := syscall.Syscall(procCM_Get_Parent.Addr(), 3, uintptr(unsafe.Pointer(outParentDev)), uintptr(dev), uintptr(flags)) + cmErr = cmError(r0) + return +} + +func cmGetDeviceIDSize(outLen *uint32, dev devInstance, flags uint32) (cmErr cmError) { + r0, _, _ := syscall.Syscall(procCM_Get_Device_ID_Size.Addr(), 3, uintptr(unsafe.Pointer(outLen)), uintptr(dev), uintptr(flags)) + cmErr = cmError(r0) + return +} + +func cmGetDeviceID(dev devInstance, buffer unsafe.Pointer, bufferSize uint32, flags uint32) (err cmError) { + r0, _, _ := syscall.Syscall6(procCM_Get_Device_IDW.Addr(), 4, uintptr(dev), uintptr(buffer), uintptr(bufferSize), uintptr(flags), 0, 0) + err = cmError(r0) + return +} + +func cmMapCrToWin32Err(cmErr cmError, defaultErr uint32) (err uint32) { + r0, _, _ := syscall.Syscall(procCM_MapCrToWin32Err.Addr(), 2, uintptr(cmErr), uintptr(defaultErr), 0) + err = uint32(r0) + return +} diff --git a/software/vendor/go.bug.st/serial/enumerator/usb_darwin.go b/software/vendor/go.bug.st/serial/enumerator/usb_darwin.go new file mode 100644 index 0000000..d9adb1b --- /dev/null +++ b/software/vendor/go.bug.st/serial/enumerator/usb_darwin.go @@ -0,0 +1,261 @@ +// +// Copyright 2014-2023 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +package enumerator + +// #cgo LDFLAGS: -framework CoreFoundation -framework IOKit +// #include <IOKit/IOKitLib.h> +// #include <CoreFoundation/CoreFoundation.h> +// #include <stdlib.h> +import "C" +import ( + "errors" + "fmt" + "time" + "unsafe" +) + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + var ports []*PortDetails + + services, err := getAllServices("IOSerialBSDClient") + if err != nil { + return nil, &PortEnumerationError{causedBy: err} + } + for _, service := range services { + defer service.Release() + + port, err := extractPortInfo(io_registry_entry_t(service)) + if err != nil { + return nil, &PortEnumerationError{causedBy: err} + } + ports = append(ports, port) + } + return ports, nil +} + +func extractPortInfo(service io_registry_entry_t) (*PortDetails, error) { + port := &PortDetails{} + // If called too early the port may still not be ready or fully enumerated + // so we retry 5 times before returning error. + for retries := 5; retries > 0; retries-- { + name, err := service.GetStringProperty("IOCalloutDevice") + if err == nil { + port.Name = name + break + } + if retries == 0 { + return nil, fmt.Errorf("error extracting port info from device: %w", err) + } + time.Sleep(50 * time.Millisecond) + } + port.IsUSB = false + + validUSBDeviceClass := map[string]bool{ + "IOUSBDevice": true, + "IOUSBHostDevice": true, + } + usbDevice := service + var searchErr error + for !validUSBDeviceClass[usbDevice.GetClass()] { + if usbDevice, searchErr = usbDevice.GetParent("IOService"); searchErr != nil { + break + } + } + if searchErr == nil { + // It's an IOUSBDevice + vid, _ := usbDevice.GetIntProperty("idVendor", C.kCFNumberSInt16Type) + pid, _ := usbDevice.GetIntProperty("idProduct", C.kCFNumberSInt16Type) + serialNumber, _ := usbDevice.GetStringProperty("USB Serial Number") + //product, _ := usbDevice.GetStringProperty("USB Product Name") + //manufacturer, _ := usbDevice.GetStringProperty("USB Vendor Name") + //fmt.Println(product + " - " + manufacturer) + + port.IsUSB = true + port.VID = fmt.Sprintf("%04X", vid) + port.PID = fmt.Sprintf("%04X", pid) + port.SerialNumber = serialNumber + } + return port, nil +} + +func getAllServices(serviceType string) ([]io_object_t, error) { + i, err := getMatchingServices(serviceMatching(serviceType)) + if err != nil { + return nil, err + } + defer i.Release() + + var services []io_object_t + tries := 0 + for tries < 5 { + // Extract all elements from iterator + if service, ok := i.Next(); ok { + services = append(services, service) + continue + } + // If the list of services is empty or the iterator is still valid return the result + if len(services) == 0 || i.IsValid() { + return services, nil + } + // Otherwise empty the result and retry + for _, s := range services { + s.Release() + } + services = []io_object_t{} + i.Reset() + tries++ + } + // Give up if the iteration continues to fail... + return nil, fmt.Errorf("IOServiceGetMatchingServices failed, data changed while iterating") +} + +// serviceMatching create a matching dictionary that specifies an IOService class match. +func serviceMatching(serviceType string) C.CFMutableDictionaryRef { + t := C.CString(serviceType) + defer C.free(unsafe.Pointer(t)) + return C.IOServiceMatching(t) +} + +// getMatchingServices look up registered IOService objects that match a matching dictionary. +func getMatchingServices(matcher C.CFMutableDictionaryRef) (io_iterator_t, error) { + var i C.io_iterator_t + err := C.IOServiceGetMatchingServices(C.kIOMasterPortDefault, C.CFDictionaryRef(matcher), &i) + if err != C.KERN_SUCCESS { + return 0, fmt.Errorf("IOServiceGetMatchingServices failed (code %d)", err) + } + return io_iterator_t(i), nil +} + +// CFStringRef + +type cfStringRef C.CFStringRef + +func cfStringCreateWithString(s string) cfStringRef { + c := C.CString(s) + defer C.free(unsafe.Pointer(c)) + return cfStringRef(C.CFStringCreateWithCString( + C.kCFAllocatorDefault, c, C.kCFStringEncodingMacRoman)) +} + +func (ref cfStringRef) Release() { + C.CFRelease(C.CFTypeRef(ref)) +} + +// CFTypeRef + +type cfTypeRef C.CFTypeRef + +func (ref cfTypeRef) Release() { + C.CFRelease(C.CFTypeRef(ref)) +} + +// io_registry_entry_t + +type io_registry_entry_t C.io_registry_entry_t + +func (me *io_registry_entry_t) GetParent(plane string) (io_registry_entry_t, error) { + cPlane := C.CString(plane) + defer C.free(unsafe.Pointer(cPlane)) + var parent C.io_registry_entry_t + err := C.IORegistryEntryGetParentEntry(C.io_registry_entry_t(*me), cPlane, &parent) + if err != 0 { + return 0, errors.New("No parent device available") + } + return io_registry_entry_t(parent), nil +} + +func (me *io_registry_entry_t) CreateCFProperty(key string) (cfTypeRef, error) { + k := cfStringCreateWithString(key) + defer k.Release() + property := C.IORegistryEntryCreateCFProperty(C.io_registry_entry_t(*me), C.CFStringRef(k), C.kCFAllocatorDefault, 0) + if property == 0 { + return 0, errors.New("Property not found: " + key) + } + return cfTypeRef(property), nil +} + +func (me *io_registry_entry_t) GetStringProperty(key string) (string, error) { + property, err := me.CreateCFProperty(key) + if err != nil { + return "", err + } + defer property.Release() + + if ptr := C.CFStringGetCStringPtr(C.CFStringRef(property), 0); ptr != nil { + return C.GoString(ptr), nil + } + // in certain circumstances CFStringGetCStringPtr may return NULL + // and we must retrieve the string by copy + buff := make([]C.char, 1024) + if C.CFStringGetCString(C.CFStringRef(property), &buff[0], 1024, 0) != C.true { + return "", fmt.Errorf("Property '%s' can't be converted", key) + } + return C.GoString(&buff[0]), nil +} + +func (me *io_registry_entry_t) GetIntProperty(key string, intType C.CFNumberType) (int, error) { + property, err := me.CreateCFProperty(key) + if err != nil { + return 0, err + } + defer property.Release() + var res int + if C.CFNumberGetValue((C.CFNumberRef)(property), intType, unsafe.Pointer(&res)) != C.true { + return res, fmt.Errorf("Property '%s' can't be converted or has been truncated", key) + } + return res, nil +} + +func (me *io_registry_entry_t) Release() { + C.IOObjectRelease(C.io_object_t(*me)) +} + +func (me *io_registry_entry_t) GetClass() string { + class := make([]C.char, 1024) + C.IOObjectGetClass(C.io_object_t(*me), &class[0]) + return C.GoString(&class[0]) +} + +// io_iterator_t + +type io_iterator_t C.io_iterator_t + +// IsValid checks if an iterator is still valid. +// Some iterators will be made invalid if changes are made to the +// structure they are iterating over. This function checks the iterator +// is still valid and should be called when Next returns zero. +// An invalid iterator can be Reset and the iteration restarted. +func (me *io_iterator_t) IsValid() bool { + return C.IOIteratorIsValid(C.io_iterator_t(*me)) == C.true +} + +func (me *io_iterator_t) Reset() { + C.IOIteratorReset(C.io_iterator_t(*me)) +} + +func (me *io_iterator_t) Next() (io_object_t, bool) { + res := C.IOIteratorNext(C.io_iterator_t(*me)) + return io_object_t(res), res != 0 +} + +func (me *io_iterator_t) Release() { + C.IOObjectRelease(C.io_object_t(*me)) +} + +// io_object_t + +type io_object_t C.io_object_t + +func (me *io_object_t) Release() { + C.IOObjectRelease(C.io_object_t(*me)) +} + +func (me *io_object_t) GetClass() string { + class := make([]C.char, 1024) + C.IOObjectGetClass(C.io_object_t(*me), &class[0]) + return C.GoString(&class[0]) +} diff --git a/software/vendor/go.bug.st/serial/enumerator/usb_freebsd.go b/software/vendor/go.bug.st/serial/enumerator/usb_freebsd.go new file mode 100644 index 0000000..db5d96c --- /dev/null +++ b/software/vendor/go.bug.st/serial/enumerator/usb_freebsd.go @@ -0,0 +1,12 @@ +// +// Copyright 2014-2023 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +package enumerator + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + // TODO + return nil, &PortEnumerationError{} +} diff --git a/software/vendor/go.bug.st/serial/enumerator/usb_linux.go b/software/vendor/go.bug.st/serial/enumerator/usb_linux.go new file mode 100644 index 0000000..f756367 --- /dev/null +++ b/software/vendor/go.bug.st/serial/enumerator/usb_linux.go @@ -0,0 +1,109 @@ +// +// Copyright 2014-2023 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +package enumerator + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + + "go.bug.st/serial" +) + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + // Retrieve the port list + ports, err := serial.GetPortsList() + if err != nil { + return nil, &PortEnumerationError{causedBy: err} + } + + var res []*PortDetails + for _, port := range ports { + details, err := nativeGetPortDetails(port) + if err != nil { + return nil, &PortEnumerationError{causedBy: err} + } + res = append(res, details) + } + return res, nil +} + +func nativeGetPortDetails(portPath string) (*PortDetails, error) { + portName := filepath.Base(portPath) + devicePath := fmt.Sprintf("/sys/class/tty/%s/device", portName) + if _, err := os.Stat(devicePath); err != nil { + return &PortDetails{}, nil + } + realDevicePath, err := filepath.EvalSymlinks(devicePath) + if err != nil { + return nil, fmt.Errorf("Can't determine real path of %s: %s", devicePath, err.Error()) + } + subSystemPath, err := filepath.EvalSymlinks(filepath.Join(realDevicePath, "subsystem")) + if err != nil { + return nil, fmt.Errorf("Can't determine real path of %s: %s", filepath.Join(realDevicePath, "subsystem"), err.Error()) + } + subSystem := filepath.Base(subSystemPath) + + result := &PortDetails{Name: portPath} + switch subSystem { + case "usb-serial": + err := parseUSBSysFS(filepath.Dir(filepath.Dir(realDevicePath)), result) + return result, err + case "usb": + err := parseUSBSysFS(filepath.Dir(realDevicePath), result) + return result, err + // TODO: other cases? + default: + return result, nil + } +} + +func parseUSBSysFS(usbDevicePath string, details *PortDetails) error { + vid, err := readLine(filepath.Join(usbDevicePath, "idVendor")) + if err != nil { + return err + } + pid, err := readLine(filepath.Join(usbDevicePath, "idProduct")) + if err != nil { + return err + } + serial, err := readLine(filepath.Join(usbDevicePath, "serial")) + if err != nil { + return err + } + //manufacturer, err := readLine(filepath.Join(usbDevicePath, "manufacturer")) + //if err != nil { + // return err + //} + //product, err := readLine(filepath.Join(usbDevicePath, "product")) + //if err != nil { + // return err + //} + + details.IsUSB = true + details.VID = vid + details.PID = pid + details.SerialNumber = serial + //details.Manufacturer = manufacturer + //details.Product = product + return nil +} + +func readLine(filename string) (string, error) { + file, err := os.Open(filename) + if os.IsNotExist(err) { + return "", nil + } + if err != nil { + return "", err + } + defer file.Close() + reader := bufio.NewReader(file) + line, _, err := reader.ReadLine() + return string(line), err +} diff --git a/software/vendor/go.bug.st/serial/enumerator/usb_openbsd.go b/software/vendor/go.bug.st/serial/enumerator/usb_openbsd.go new file mode 100644 index 0000000..db5d96c --- /dev/null +++ b/software/vendor/go.bug.st/serial/enumerator/usb_openbsd.go @@ -0,0 +1,12 @@ +// +// Copyright 2014-2023 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +package enumerator + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + // TODO + return nil, &PortEnumerationError{} +} diff --git a/software/vendor/go.bug.st/serial/enumerator/usb_windows.go b/software/vendor/go.bug.st/serial/enumerator/usb_windows.go new file mode 100644 index 0000000..7883e45 --- /dev/null +++ b/software/vendor/go.bug.st/serial/enumerator/usb_windows.go @@ -0,0 +1,359 @@ +// +// Copyright 2014-2023 Cristian Maglie. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// + +package enumerator + +import ( + "fmt" + "regexp" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +func parseDeviceID(deviceID string, details *PortDetails) { + // Windows stock USB-CDC driver + if len(deviceID) >= 3 && deviceID[:3] == "USB" { + re := regexp.MustCompile("VID_(....)&PID_(....)(\\\\(\\w+)$)?").FindAllStringSubmatch(deviceID, -1) + if re == nil || len(re[0]) < 2 { + // Silently ignore unparsable strings + return + } + details.IsUSB = true + details.VID = re[0][1] + details.PID = re[0][2] + if len(re[0]) >= 4 { + details.SerialNumber = re[0][4] + } + return + } + + // FTDI driver + if len(deviceID) >= 7 && deviceID[:7] == "FTDIBUS" { + re := regexp.MustCompile("VID_(....)\\+PID_(....)(\\+(\\w+))?").FindAllStringSubmatch(deviceID, -1) + if re == nil || len(re[0]) < 2 { + // Silently ignore unparsable strings + return + } + details.IsUSB = true + details.VID = re[0][1] + details.PID = re[0][2] + if len(re[0]) >= 4 { + details.SerialNumber = re[0][4] + } + return + } + + // Other unidentified device type +} + +// setupapi based +// -------------- + +//sys setupDiClassGuidsFromNameInternal(class string, guid *guid, guidSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiClassGuidsFromNameW +//sys setupDiGetClassDevs(guid *guid, enumerator *string, hwndParent uintptr, flags uint32) (set devicesSet, err error) = setupapi.SetupDiGetClassDevsW +//sys setupDiDestroyDeviceInfoList(set devicesSet) (err error) = setupapi.SetupDiDestroyDeviceInfoList +//sys setupDiEnumDeviceInfo(set devicesSet, index uint32, info *devInfoData) (err error) = setupapi.SetupDiEnumDeviceInfo +//sys setupDiGetDeviceInstanceId(set devicesSet, devInfo *devInfoData, devInstanceId unsafe.Pointer, devInstanceIdSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiGetDeviceInstanceIdW +//sys setupDiOpenDevRegKey(set devicesSet, devInfo *devInfoData, scope dicsScope, hwProfile uint32, keyType uint32, samDesired regsam) (hkey syscall.Handle, err error) = setupapi.SetupDiOpenDevRegKey +//sys setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, property deviceProperty, propertyType *uint32, outValue *byte, bufSize uint32, reqSize *uint32) (res bool) = setupapi.SetupDiGetDeviceRegistryPropertyW + +//sys cmGetParent(outParentDev *devInstance, dev devInstance, flags uint32) (cmErr cmError) = cfgmgr32.CM_Get_Parent +//sys cmGetDeviceIDSize(outLen *uint32, dev devInstance, flags uint32) (cmErr cmError) = cfgmgr32.CM_Get_Device_ID_Size +//sys cmGetDeviceID(dev devInstance, buffer unsafe.Pointer, bufferSize uint32, flags uint32) (err cmError) = cfgmgr32.CM_Get_Device_IDW +//sys cmMapCrToWin32Err(cmErr cmError, defaultErr uint32) (err uint32) = cfgmgr32.CM_MapCrToWin32Err + +// Device registry property codes +// (Codes marked as read-only (R) may only be used for +// SetupDiGetDeviceRegistryProperty) +// +// These values should cover the same set of registry properties +// as defined by the CM_DRP codes in cfgmgr32.h. +// +// Note that SPDRP codes are zero based while CM_DRP codes are one based! +type deviceProperty uint32 + +const ( + spdrpDeviceDesc deviceProperty = 0x00000000 // DeviceDesc = R/W + spdrpHardwareID = 0x00000001 // HardwareID = R/W + spdrpCompatibleIDS = 0x00000002 // CompatibleIDs = R/W + spdrpUnused0 = 0x00000003 // Unused + spdrpService = 0x00000004 // Service = R/W + spdrpUnused1 = 0x00000005 // Unused + spdrpUnused2 = 0x00000006 // Unused + spdrpClass = 0x00000007 // Class = R--tied to ClassGUID + spdrpClassGUID = 0x00000008 // ClassGUID = R/W + spdrpDriver = 0x00000009 // Driver = R/W + spdrpConfigFlags = 0x0000000A // ConfigFlags = R/W + spdrpMFG = 0x0000000B // Mfg = R/W + spdrpFriendlyName = 0x0000000C // FriendlyName = R/W + spdrpLocationIinformation = 0x0000000D // LocationInformation = R/W + spdrpPhysicalDeviceObjectName = 0x0000000E // PhysicalDeviceObjectName = R + spdrpCapabilities = 0x0000000F // Capabilities = R + spdrpUINumber = 0x00000010 // UiNumber = R + spdrpUpperFilters = 0x00000011 // UpperFilters = R/W + spdrpLowerFilters = 0x00000012 // LowerFilters = R/W + spdrpBusTypeGUID = 0x00000013 // BusTypeGUID = R + spdrpLegactBusType = 0x00000014 // LegacyBusType = R + spdrpBusNumber = 0x00000015 // BusNumber = R + spdrpEnumeratorName = 0x00000016 // Enumerator Name = R + spdrpSecurity = 0x00000017 // Security = R/W, binary form + spdrpSecuritySDS = 0x00000018 // Security = W, SDS form + spdrpDevType = 0x00000019 // Device Type = R/W + spdrpExclusive = 0x0000001A // Device is exclusive-access = R/W + spdrpCharacteristics = 0x0000001B // Device Characteristics = R/W + spdrpAddress = 0x0000001C // Device Address = R + spdrpUINumberDescFormat = 0x0000001D // UiNumberDescFormat = R/W + spdrpDevicePowerData = 0x0000001E // Device Power Data = R + spdrpRemovalPolicy = 0x0000001F // Removal Policy = R + spdrpRemovalPolicyHWDefault = 0x00000020 // Hardware Removal Policy = R + spdrpRemovalPolicyOverride = 0x00000021 // Removal Policy Override = RW + spdrpInstallState = 0x00000022 // Device Install State = R + spdrpLocationPaths = 0x00000023 // Device Location Paths = R + spdrpBaseContainerID = 0x00000024 // Base ContainerID = R + + spdrpMaximumProperty = 0x00000025 // Upper bound on ordinals +) + +// Values specifying the scope of a device property change +type dicsScope uint32 + +const ( + dicsFlagGlobal dicsScope = 0x00000001 // make change in all hardware profiles + dicsFlagConfigSspecific = 0x00000002 // make change in specified profile only + dicsFlagConfigGeneral = 0x00000004 // 1 or more hardware profile-specific +) + +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878(v=vs.85).aspx +type regsam uint32 + +const ( + keyAllAccess regsam = 0xF003F + keyCreateLink = 0x00020 + keyCreateSubKey = 0x00004 + keyEnumerateSubKeys = 0x00008 + keyExecute = 0x20019 + keyNotify = 0x00010 + keyQueryValue = 0x00001 + keyRead = 0x20019 + keySetValue = 0x00002 + keyWOW64_32key = 0x00200 + keyWOW64_64key = 0x00100 + keyWrite = 0x20006 +) + +// KeyType values for SetupDiCreateDevRegKey, SetupDiOpenDevRegKey, and +// SetupDiDeleteDevRegKey. +const ( + diregDev = 0x00000001 // Open/Create/Delete device key + diregDrv = 0x00000002 // Open/Create/Delete driver key + diregBoth = 0x00000004 // Delete both driver and Device key +) + +// https://msdn.microsoft.com/it-it/library/windows/desktop/aa373931(v=vs.85).aspx +type guid struct { + data1 uint32 + data2 uint16 + data3 uint16 + data4 [8]byte +} + +func (g guid) String() string { + return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + g.data1, g.data2, g.data3, + g.data4[0], g.data4[1], g.data4[2], g.data4[3], + g.data4[4], g.data4[5], g.data4[6], g.data4[7]) +} + +func classGuidsFromName(className string) ([]guid, error) { + // Determine the number of GUIDs for className + n := uint32(0) + if err := setupDiClassGuidsFromNameInternal(className, nil, 0, &n); err != nil { + // ignore error: UIDs array size too small + } + + res := make([]guid, n) + err := setupDiClassGuidsFromNameInternal(className, &res[0], n, &n) + return res, err +} + +const ( + digcfDefault = 0x00000001 // only valid with digcfDeviceInterface + digcfPresent = 0x00000002 + digcfAllClasses = 0x00000004 + digcfProfile = 0x00000008 + digcfDeviceInterface = 0x00000010 +) + +type devicesSet syscall.Handle + +func (g *guid) getDevicesSet() (devicesSet, error) { + return setupDiGetClassDevs(g, nil, 0, digcfPresent) +} + +func (set devicesSet) destroy() { + setupDiDestroyDeviceInfoList(set) +} + +type cmError uint32 + +// https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx +type devInfoData struct { + size uint32 + guid guid + devInst devInstance + reserved uintptr +} + +type devInstance uint32 + +func cmConvertError(cmErr cmError) error { + if cmErr == 0 { + return nil + } + winErr := cmMapCrToWin32Err(cmErr, 0) + return fmt.Errorf("error %d", winErr) +} + +func (dev devInstance) getParent() (devInstance, error) { + var res devInstance + errN := cmGetParent(&res, dev, 0) + return res, cmConvertError(errN) +} + +func (dev devInstance) GetDeviceID() (string, error) { + var size uint32 + cmErr := cmGetDeviceIDSize(&size, dev, 0) + if err := cmConvertError(cmErr); err != nil { + return "", err + } + buff := make([]uint16, size) + cmErr = cmGetDeviceID(dev, unsafe.Pointer(&buff[0]), uint32(len(buff)), 0) + if err := cmConvertError(cmErr); err != nil { + return "", err + } + return windows.UTF16ToString(buff[:]), nil +} + +type deviceInfo struct { + set devicesSet + data devInfoData +} + +func (set devicesSet) getDeviceInfo(index int) (*deviceInfo, error) { + result := &deviceInfo{set: set} + + result.data.size = uint32(unsafe.Sizeof(result.data)) + err := setupDiEnumDeviceInfo(set, uint32(index), &result.data) + return result, err +} + +func (dev *deviceInfo) getInstanceID() (string, error) { + n := uint32(0) + setupDiGetDeviceInstanceId(dev.set, &dev.data, nil, 0, &n) + buff := make([]uint16, n) + if err := setupDiGetDeviceInstanceId(dev.set, &dev.data, unsafe.Pointer(&buff[0]), uint32(len(buff)), &n); err != nil { + return "", err + } + return windows.UTF16ToString(buff[:]), nil +} + +func (dev *deviceInfo) openDevRegKey(scope dicsScope, hwProfile uint32, keyType uint32, samDesired regsam) (syscall.Handle, error) { + return setupDiOpenDevRegKey(dev.set, &dev.data, scope, hwProfile, keyType, samDesired) +} + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + guids, err := classGuidsFromName("Ports") + if err != nil { + return nil, &PortEnumerationError{causedBy: err} + } + + var res []*PortDetails + for _, g := range guids { + devsSet, err := g.getDevicesSet() + if err != nil { + return nil, &PortEnumerationError{causedBy: err} + } + defer devsSet.destroy() + + for i := 0; ; i++ { + device, err := devsSet.getDeviceInfo(i) + if err != nil { + break + } + details := &PortDetails{} + portName, err := retrievePortNameFromDevInfo(device) + if err != nil { + continue + } + if len(portName) < 3 || portName[0:3] != "COM" { + // Accept only COM ports + continue + } + details.Name = portName + + if err := retrievePortDetailsFromDevInfo(device, details); err != nil { + return nil, &PortEnumerationError{causedBy: err} + } + res = append(res, details) + } + } + return res, nil +} + +func retrievePortNameFromDevInfo(device *deviceInfo) (string, error) { + h, err := device.openDevRegKey(dicsFlagGlobal, 0, diregDev, keyRead) + if err != nil { + return "", err + } + defer syscall.RegCloseKey(h) + + var name [1024]uint16 + nameP := (*byte)(unsafe.Pointer(&name[0])) + nameSize := uint32(len(name) * 2) + if err := syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr("PortName"), nil, nil, nameP, &nameSize); err != nil { + return "", err + } + return syscall.UTF16ToString(name[:]), nil +} + +func retrievePortDetailsFromDevInfo(device *deviceInfo, details *PortDetails) error { + deviceID, err := device.getInstanceID() + if err != nil { + return err + } + parseDeviceID(deviceID, details) + + // On composite USB devices the serial number is usually reported on the parent + // device, so let's navigate up one level and see if we can get this information + if details.IsUSB && details.SerialNumber == "" { + if parentInfo, err := device.data.devInst.getParent(); err == nil { + if parentDeviceID, err := parentInfo.GetDeviceID(); err == nil { + d := &PortDetails{} + parseDeviceID(parentDeviceID, d) + if details.VID == d.VID && details.PID == d.PID { + details.SerialNumber = d.SerialNumber + } + } + } + } + + /* spdrpDeviceDesc returns a generic name, e.g.: "CDC-ACM", which will be the same for 2 identical devices attached + while spdrpFriendlyName returns a specific name, e.g.: "CDC-ACM (COM44)", + the result of spdrpFriendlyName is therefore unique and suitable as an alternative string to for a port choice */ + n := uint32(0) + setupDiGetDeviceRegistryProperty(device.set, &device.data, spdrpFriendlyName /* spdrpDeviceDesc */, nil, nil, 0, &n) + if n > 0 { + buff := make([]uint16, n*2) + buffP := (*byte)(unsafe.Pointer(&buff[0])) + if setupDiGetDeviceRegistryProperty(device.set, &device.data, spdrpFriendlyName /* spdrpDeviceDesc */, nil, buffP, n, &n) { + details.Product = syscall.UTF16ToString(buff[:]) + } + } + + return nil +} |