USB detection for Windows (based on setupapi)

This commit is contained in:
Cristian Maglie
2016-10-22 18:36:35 +02:00
parent 2d0a54e6c8
commit ad7966a434
5 changed files with 444 additions and 10 deletions

View File

@@ -23,6 +23,7 @@ matrix:
before_install:
script:
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go get github.com/stretchr/testify/require
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go get golang.org/x/sys/windows
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go build -v ./...
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go test -c -v go.bug.st/serial.v1

View File

@@ -23,7 +23,7 @@ type windowsPort struct {
handle syscall.Handle
}
//go:generate go run extras/mksyscall_windows.go -output syscall_windows.go serial_windows.go
//go:generate go run extras/mksyscall_windows.go -output syscall_windows.go serial_windows.go usb_windows.go
//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) = advapi32.RegEnumValueW

View File

@@ -14,13 +14,21 @@ var _ unsafe.Pointer
var (
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modsetupapi = windows.NewLazySystemDLL("setupapi.dll")
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
procGetCommState = modkernel32.NewProc("GetCommState")
procSetCommState = modkernel32.NewProc("SetCommState")
procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts")
procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction")
procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus")
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
procGetCommState = modkernel32.NewProc("GetCommState")
procSetCommState = modkernel32.NewProc("SetCommState")
procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts")
procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction")
procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus")
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")
)
func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) {
@@ -78,3 +86,92 @@ func getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) {
res = r0 != 0
return
}
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 = error(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 = error(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 = error(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 = error(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 = error(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 = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, property deviceProperty, propertyType *uint32, outValue *byte, outSize *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(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(reqSize)), 0, 0)
res = r0 != 0
return
}

View File

@@ -6,7 +6,294 @@
package serial // import "go.bug.st/serial.v1"
func nativeGetDetailedPortsList() ([]*PortDetails, error) {
// TODO
return nil, &PortError{code: FunctionNotImplemented}
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
}
//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, outSize *uint32, reqSize *uint32) (res bool) = setupapi.SetupDiGetDeviceRegistryPropertyW
// 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)
}
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx
type devInfoData struct {
size uint32
guid guid
devInst uint32
reserved uintptr
}
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, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
}
var res []*PortDetails
for _, g := range guids {
devsSet, err := g.getDevicesSet()
if err != nil {
return nil, &PortError{code: ErrorEnumeratingPorts, 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, &PortError{code: ErrorEnumeratingPorts, 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)
var friendlyName [1024]uint16
friendlyNameP := (*byte)(unsafe.Pointer(&friendlyName[0]))
friendlyNameSize := uint32(len(friendlyName) * 2)
if setupDiGetDeviceRegistryProperty(device.set, &device.data, spdrpDeviceDesc /* spdrpFriendlyName */, nil, friendlyNameP, &friendlyNameSize, nil) {
details.Product = syscall.UTF16ToString(friendlyName[:])
}
return nil
}

49
usb_windows_test.go Normal file
View File

@@ -0,0 +1,49 @@
//
// Copyright 2014-2016 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 serial // import "go.bug.st/serial.v1"
import (
"testing"
"github.com/stretchr/testify/require"
)
func parseAndReturnDeviceID(deviceID string) *PortDetails {
res := &PortDetails{}
parseDeviceID(deviceID, res)
return res
}
func TestParseDeviceID(t *testing.T) {
r := require.New(t)
test := func(deviceId, vid, pid, serialNo string) {
res := parseAndReturnDeviceID(deviceId)
r.True(res.IsUSB)
r.Equal(vid, res.VID)
r.Equal(pid, res.PID)
r.Equal(serialNo, res.SerialNumber)
}
test("FTDIBUS\\VID_0403+PID_6001+A6004CCFA\\0000", "0403", "6001", "A6004CCFA")
test("USB\\VID_16C0&PID_0483\\12345", "16C0", "0483", "12345")
test("USB\\VID_2341&PID_0000\\64936333936351400000", "2341", "0000", "64936333936351400000")
test("USB\\VID_2341&PID_0000\\6493234373835191F1F1", "2341", "0000", "6493234373835191F1F1")
test("USB\\VID_2341&PID_804E&MI_00\\6&279A3900&0&0000", "2341", "804E", "")
test("USB\\VID_2341&PID_004E\\5&C3DC240&0&1", "2341", "004E", "")
test("USB\\VID_03EB&PID_2111&MI_01\\6&21F3553F&0&0001", "03EB", "2111", "") // Atmel EDBG
test("USB\\VID_2341&PID_804D&MI_00\\6&1026E213&0&0000", "2341", "804D", "")
test("USB\\VID_2341&PID_004D\\5&C3DC240&0&1", "2341", "004D", "")
test("USB\\VID_067B&PID_2303\\6&2C4CB384&0&3", "067B", "2303", "") // PL2303
}
func TestParseDeviceIDWithInvalidStrings(t *testing.T) {
r := require.New(t)
res := parseAndReturnDeviceID("ABC")
r.False(res.IsUSB)
res2 := parseAndReturnDeviceID("USB")
r.False(res2.IsUSB)
}