Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions enumerator/enumerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,38 @@ type PortDetails struct {
// SerialNumber is the USB serial number, when available.
SerialNumber string
// Configuration is the USB configuration string, when available.
// Requires active USB probing enabled.
Configuration string
// Manufacturer is the USB iManufacturer string, when available.
// Requires active USB probing enabled.
Manufacturer string
// Product is the USB iProduct string, when available.
// Requires active USB probing enabled.
Product string
}

// All is a vid/pid filter that accepts all devices
var All = func(vid, pid string) bool { return true }

// 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()
//
// Getting some USB fields requires active USB probing (see PortDetails
// struct), which may interfere with correct operation on some devices.
// Active USB probing is disabled by default, to enable it you must provide
// at least one filter function to allow the probing of specific devices based on
// vid and pid. If no filters are provided then no devices will be actively probed.
// If a device match any of the filters provided then the device will be actively probed.
func GetDetailedPortsList(activeUSBProbeFilters ...func(vid, pid string) bool) ([]*PortDetails, error) {
return nativeGetDetailedPortsList(func(vid, pid string) bool {
for _, filter := range activeUSBProbeFilters {
if filter(vid, pid) {
return true
}
}
return false
})
}

// PortEnumerationError is the error type for serial ports enumeration
Expand Down
10 changes: 9 additions & 1 deletion enumerator/example_getdetailedportlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ import (
)

func ExampleGetDetailedPortsList() {
ports, err := enumerator.GetDetailedPortsList()
// Passing enumerator.All actively probes every USB device to retrieve the
// Manufacturer, Product and Configuration fields. This may interfere with
// the normal operation of some devices, so in production code you should
// prefer a filter that only allows probing specific VID/PID pairs, e.g.:
//
// enumerator.GetDetailedPortsList(func(vid, pid string) bool {
// return vid == "2341" // only probe Arduino devices
// })
ports, err := enumerator.GetDetailedPortsList(enumerator.All)
if err != nil {
log.Fatal(err)
}
Expand Down
16 changes: 11 additions & 5 deletions enumerator/usb_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import (
"unsafe"
)

func nativeGetDetailedPortsList() ([]*PortDetails, error) {
func nativeGetDetailedPortsList(shouldProbeUSB func(vid, pid string) bool) ([]*PortDetails, error) {
var ports []*PortDetails

services, err := getAllServices("IOSerialBSDClient")
Expand All @@ -63,7 +63,7 @@ func nativeGetDetailedPortsList() ([]*PortDetails, error) {
}()

for _, service := range services {
port, err := extractPortInfo(io_registry_entry_t(service))
port, err := extractPortInfo(io_registry_entry_t(service), shouldProbeUSB)
if err != nil {
return nil, &PortEnumerationError{causedBy: err}
}
Expand All @@ -72,7 +72,7 @@ func nativeGetDetailedPortsList() ([]*PortDetails, error) {
return ports, nil
}

func extractPortInfo(service io_registry_entry_t) (*PortDetails, error) {
func extractPortInfo(service io_registry_entry_t, shouldProbeUSB func(vid, pid string) bool) (*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.
Expand Down Expand Up @@ -116,15 +116,21 @@ func extractPortInfo(service io_registry_entry_t) (*PortDetails, error) {
serialNumber, _ := usbDevice.GetStringProperty("kUSBSerialNumberString")
vendor, _ := usbDevice.GetStringProperty("kUSBVendorString")
product, _ := usbDevice.GetStringProperty("kUSBProductString")
configuration, _ := usbDevice.GetUSBConfigurationString()

port.IsUSB = true
port.VID = fmt.Sprintf("%04X", vid)
port.PID = fmt.Sprintf("%04X", pid)
port.SerialNumber = serialNumber
port.Manufacturer = vendor
port.Product = product
port.Configuration = configuration

// Retrieving the USB configuration string requires actively opening
// the device and issuing control requests, which may interfere with
// the device's normal operation. Only do this if the caller
// explicitly allowed probing for this VID/PID.
if shouldProbeUSB(port.VID, port.PID) {
port.Configuration, _ = usbDevice.GetUSBConfigurationString()
}
}
return port, nil
}
Expand Down
2 changes: 1 addition & 1 deletion enumerator/usb_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

package enumerator

func nativeGetDetailedPortsList() ([]*PortDetails, error) {
func nativeGetDetailedPortsList(_ func(vid, pid string) bool) ([]*PortDetails, error) {
// TODO
return nil, &PortEnumerationError{}
}
8 changes: 6 additions & 2 deletions enumerator/usb_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ import (
"go.bug.st/serial"
)

func nativeGetDetailedPortsList() ([]*PortDetails, error) {
// Retrieve the port list
func nativeGetDetailedPortsList(_ func(vid, pid string) bool) ([]*PortDetails, error) {
// Retrieve the port list.
// Note: on Linux all the USB details (including Configuration, Manufacturer
// and Product) are read from sysfs, which is populated by the kernel at
// enumeration time and does not require actively probing the device, so
// the active-probe filter is not needed on this platform.
ports, err := serial.GetPortsList()
if err != nil {
return nil, &PortEnumerationError{causedBy: err}
Expand Down
2 changes: 1 addition & 1 deletion enumerator/usb_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

package enumerator

func nativeGetDetailedPortsList() ([]*PortDetails, error) {
func nativeGetDetailedPortsList(_ func(vid, pid string) bool) ([]*PortDetails, error) {
// TODO
return nil, &PortEnumerationError{}
}
2 changes: 1 addition & 1 deletion enumerator/usb_wasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@

package enumerator

func nativeGetDetailedPortsList() ([]*PortDetails, error) {
func nativeGetDetailedPortsList(_ func(vid, pid string) bool) ([]*PortDetails, error) {
return nil, &PortEnumerationError{}
}
12 changes: 8 additions & 4 deletions enumerator/usb_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (dev *deviceInfo) openDevRegKey(scope windows.DICS_FLAG, hwProfile uint32,
return setupDiOpenDevRegKey(dev.set, dev.data, scope, hwProfile, keyType, samDesired)
}

func nativeGetDetailedPortsList() ([]*PortDetails, error) {
func nativeGetDetailedPortsList(shouldProbeUSB func(vid, pid string) bool) ([]*PortDetails, error) {
guids, err := windows.SetupDiClassGuidsFromNameEx("Ports", "")
if err != nil {
return nil, &PortEnumerationError{causedBy: err}
Expand Down Expand Up @@ -147,7 +147,7 @@ func nativeGetDetailedPortsList() ([]*PortDetails, error) {
}
details.Name = portName

if err := retrievePortDetailsFromDevInfo(device, details); err != nil {
if err := retrievePortDetailsFromDevInfo(device, details, shouldProbeUSB); err != nil {
return nil, &PortEnumerationError{causedBy: err}
}
res = append(res, details)
Expand All @@ -172,7 +172,7 @@ func retrievePortNameFromDevInfo(device *deviceInfo) (string, error) {
return syscall.UTF16ToString(name[:]), nil
}

func retrievePortDetailsFromDevInfo(device *deviceInfo, details *PortDetails) error {
func retrievePortDetailsFromDevInfo(device *deviceInfo, details *PortDetails, shouldProbeUSB func(vid, pid string) bool) error {
deviceID, err := device.getInstanceID()
if err != nil {
return err
Expand Down Expand Up @@ -206,7 +206,11 @@ func retrievePortDetailsFromDevInfo(device *deviceInfo, details *PortDetails) er
}
}

if details.IsUSB {
// Retrieving iManufacturer/iProduct/iConfiguration strings requires actively
// probing the USB device via the parent hub, which may interfere with the
// device's normal operation. Only do this if the caller explicitly allowed
// probing for this VID/PID.
if details.IsUSB && shouldProbeUSB(details.VID, details.PID) {
if hub, port, err := findUsbHubAndPortConnectedToDevice(device); err == nil {
defer hub.Close()

Expand Down
11 changes: 10 additions & 1 deletion portlist/portlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@
package main

import (
"flag"
"fmt"
"log"

"go.bug.st/serial/enumerator"
)

func main() {
ports, err := enumerator.GetDetailedPortsList()
probe := flag.Bool("probe", false, "actively probe USB devices to retrieve manufacturer, product and configuration strings")
flag.Parse()

var filters []func(vid, pid string) bool
if *probe {
filters = append(filters, enumerator.All)
}

ports, err := enumerator.GetDetailedPortsList(filters...)
if err != nil {
log.Fatal(err)
}
Expand Down