summaryrefslogtreecommitdiff
path: root/software/vendor/go.bug.st/serial/enumerator
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2024-05-30 16:00:32 +0200
committerxengineering <me@xengineering.eu>2024-05-30 16:00:32 +0200
commitb8ef4d11fe0d00ce0884ccf982675845b20c3ce9 (patch)
tree3beb57ff849ed74569ce325225bc819791c25a6a /software/vendor/go.bug.st/serial/enumerator
parenteab833271eeaa8d54991c11eccec9445f662a191 (diff)
downloadiot-core-b8ef4d11fe0d00ce0884ccf982675845b20c3ce9.tar
iot-core-b8ef4d11fe0d00ce0884ccf982675845b20c3ce9.tar.zst
iot-core-b8ef4d11fe0d00ce0884ccf982675845b20c3ce9.zip
software: Implement serial port detection
Diffstat (limited to 'software/vendor/go.bug.st/serial/enumerator')
-rw-r--r--software/vendor/go.bug.st/serial/enumerator/doc.go15
-rw-r--r--software/vendor/go.bug.st/serial/enumerator/enumerator.go46
-rw-r--r--software/vendor/go.bug.st/serial/enumerator/syscall_windows.go167
-rw-r--r--software/vendor/go.bug.st/serial/enumerator/usb_darwin.go261
-rw-r--r--software/vendor/go.bug.st/serial/enumerator/usb_freebsd.go12
-rw-r--r--software/vendor/go.bug.st/serial/enumerator/usb_linux.go109
-rw-r--r--software/vendor/go.bug.st/serial/enumerator/usb_openbsd.go12
-rw-r--r--software/vendor/go.bug.st/serial/enumerator/usb_windows.go359
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
+}