diff --git a/.travis.yml b/.travis.yml index 2783c85..3ad5c64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/serial_windows.go b/serial_windows.go index ed04e6b..500e85a 100644 --- a/serial_windows.go +++ b/serial_windows.go @@ -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 diff --git a/syscall_windows.go b/syscall_windows.go index dfa3f84..add2cce 100644 --- a/syscall_windows.go +++ b/syscall_windows.go @@ -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 +} diff --git a/usb_windows.go b/usb_windows.go index 8a60a19..32d78e2 100644 --- a/usb_windows.go +++ b/usb_windows.go @@ -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 } diff --git a/usb_windows_test.go b/usb_windows_test.go new file mode 100644 index 0000000..ee58d47 --- /dev/null +++ b/usb_windows_test.go @@ -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) +}