summaryrefslogtreecommitdiff
path: root/software/vendor/go.bug.st/serial/serial_windows.go
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/serial_windows.go
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/serial_windows.go')
-rw-r--r--software/vendor/go.bug.st/serial/serial_windows.go481
1 files changed, 481 insertions, 0 deletions
diff --git a/software/vendor/go.bug.st/serial/serial_windows.go b/software/vendor/go.bug.st/serial/serial_windows.go
new file mode 100644
index 0000000..35bb0c9
--- /dev/null
+++ b/software/vendor/go.bug.st/serial/serial_windows.go
@@ -0,0 +1,481 @@
+//
+// 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 serial
+
+/*
+
+// MSDN article on Serial Communications:
+// http://msdn.microsoft.com/en-us/library/ff802693.aspx
+// (alternative link) https://msdn.microsoft.com/en-us/library/ms810467.aspx
+
+// Arduino Playground article on serial communication with Windows API:
+// http://playground.arduino.cc/Interfacing/CPPWindows
+
+*/
+
+import (
+ "sync"
+ "syscall"
+ "time"
+)
+
+type windowsPort struct {
+ mu sync.Mutex
+ handle syscall.Handle
+}
+
+func nativeGetPortsList() ([]string, error) {
+ subKey, err := syscall.UTF16PtrFromString("HARDWARE\\DEVICEMAP\\SERIALCOMM\\")
+ if err != nil {
+ return nil, &PortError{code: ErrorEnumeratingPorts}
+ }
+
+ var h syscall.Handle
+ if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, subKey, 0, syscall.KEY_READ, &h); err != nil {
+ if errno, isErrno := err.(syscall.Errno); isErrno && errno == syscall.ERROR_FILE_NOT_FOUND {
+ return []string{}, nil
+ }
+ return nil, &PortError{code: ErrorEnumeratingPorts}
+ }
+ defer syscall.RegCloseKey(h)
+
+ var valuesCount uint32
+ if syscall.RegQueryInfoKey(h, nil, nil, nil, nil, nil, nil, &valuesCount, nil, nil, nil, nil) != nil {
+ return nil, &PortError{code: ErrorEnumeratingPorts}
+ }
+
+ list := make([]string, valuesCount)
+ for i := range list {
+ var data [1024]uint16
+ dataSize := uint32(len(data))
+ var name [1024]uint16
+ nameSize := uint32(len(name))
+ if regEnumValue(h, uint32(i), &name[0], &nameSize, nil, nil, &data[0], &dataSize) != nil {
+ return nil, &PortError{code: ErrorEnumeratingPorts}
+ }
+ list[i] = syscall.UTF16ToString(data[:])
+ }
+ return list, nil
+}
+
+func (port *windowsPort) Close() error {
+ port.mu.Lock()
+ defer func() {
+ port.handle = 0
+ port.mu.Unlock()
+ }()
+ if port.handle == 0 {
+ return nil
+ }
+ return syscall.CloseHandle(port.handle)
+}
+
+func (port *windowsPort) Read(p []byte) (int, error) {
+ var readed uint32
+ ev, err := createOverlappedEvent()
+ if err != nil {
+ return 0, err
+ }
+ defer syscall.CloseHandle(ev.HEvent)
+
+ err = syscall.ReadFile(port.handle, p, &readed, ev)
+ if err == syscall.ERROR_IO_PENDING {
+ err = getOverlappedResult(port.handle, ev, &readed, true)
+ }
+ switch err {
+ case nil:
+ // operation completed successfully
+ case syscall.ERROR_OPERATION_ABORTED:
+ // port may have been closed
+ return int(readed), &PortError{code: PortClosed, causedBy: err}
+ default:
+ // error happened
+ return int(readed), err
+ }
+ if readed > 0 {
+ return int(readed), nil
+ }
+
+ // Timeout
+ return 0, nil
+}
+
+func (port *windowsPort) Write(p []byte) (int, error) {
+ var writed uint32
+ ev, err := createOverlappedEvent()
+ if err != nil {
+ return 0, err
+ }
+ defer syscall.CloseHandle(ev.HEvent)
+ err = syscall.WriteFile(port.handle, p, &writed, ev)
+ if err == syscall.ERROR_IO_PENDING {
+ // wait for write to complete
+ err = getOverlappedResult(port.handle, ev, &writed, true)
+ }
+ return int(writed), err
+}
+
+func (port *windowsPort) Drain() (err error) {
+ return syscall.FlushFileBuffers(port.handle)
+}
+
+const (
+ purgeRxAbort uint32 = 0x0002
+ purgeRxClear = 0x0008
+ purgeTxAbort = 0x0001
+ purgeTxClear = 0x0004
+)
+
+func (port *windowsPort) ResetInputBuffer() error {
+ return purgeComm(port.handle, purgeRxClear|purgeRxAbort)
+}
+
+func (port *windowsPort) ResetOutputBuffer() error {
+ return purgeComm(port.handle, purgeTxClear|purgeTxAbort)
+}
+
+const (
+ dcbBinary uint32 = 0x00000001
+ dcbParity = 0x00000002
+ dcbOutXCTSFlow = 0x00000004
+ dcbOutXDSRFlow = 0x00000008
+ dcbDTRControlDisableMask = ^uint32(0x00000030)
+ dcbDTRControlEnable = 0x00000010
+ dcbDTRControlHandshake = 0x00000020
+ dcbDSRSensitivity = 0x00000040
+ dcbTXContinueOnXOFF = 0x00000080
+ dcbOutX = 0x00000100
+ dcbInX = 0x00000200
+ dcbErrorChar = 0x00000400
+ dcbNull = 0x00000800
+ dcbRTSControlDisbaleMask = ^uint32(0x00003000)
+ dcbRTSControlEnable = 0x00001000
+ dcbRTSControlHandshake = 0x00002000
+ dcbRTSControlToggle = 0x00003000
+ dcbAbortOnError = 0x00004000
+)
+
+type dcb struct {
+ DCBlength uint32
+ BaudRate uint32
+
+ // Flags field is a bitfield
+ // fBinary :1
+ // fParity :1
+ // fOutxCtsFlow :1
+ // fOutxDsrFlow :1
+ // fDtrControl :2
+ // fDsrSensitivity :1
+ // fTXContinueOnXoff :1
+ // fOutX :1
+ // fInX :1
+ // fErrorChar :1
+ // fNull :1
+ // fRtsControl :2
+ // fAbortOnError :1
+ // fDummy2 :17
+ Flags uint32
+
+ wReserved uint16
+ XonLim uint16
+ XoffLim uint16
+ ByteSize byte
+ Parity byte
+ StopBits byte
+ XonChar byte
+ XoffChar byte
+ ErrorChar byte
+ EOFChar byte
+ EvtChar byte
+ wReserved1 uint16
+}
+
+type commTimeouts struct {
+ ReadIntervalTimeout uint32
+ ReadTotalTimeoutMultiplier uint32
+ ReadTotalTimeoutConstant uint32
+ WriteTotalTimeoutMultiplier uint32
+ WriteTotalTimeoutConstant uint32
+}
+
+const (
+ noParity = 0
+ oddParity = 1
+ evenParity = 2
+ markParity = 3
+ spaceParity = 4
+)
+
+var parityMap = map[Parity]byte{
+ NoParity: noParity,
+ OddParity: oddParity,
+ EvenParity: evenParity,
+ MarkParity: markParity,
+ SpaceParity: spaceParity,
+}
+
+const (
+ oneStopBit = 0
+ one5StopBits = 1
+ twoStopBits = 2
+)
+
+var stopBitsMap = map[StopBits]byte{
+ OneStopBit: oneStopBit,
+ OnePointFiveStopBits: one5StopBits,
+ TwoStopBits: twoStopBits,
+}
+
+const (
+ commFunctionSetXOFF = 1
+ commFunctionSetXON = 2
+ commFunctionSetRTS = 3
+ commFunctionClrRTS = 4
+ commFunctionSetDTR = 5
+ commFunctionClrDTR = 6
+ commFunctionSetBreak = 8
+ commFunctionClrBreak = 9
+)
+
+const (
+ msCTSOn = 0x0010
+ msDSROn = 0x0020
+ msRingOn = 0x0040
+ msRLSDOn = 0x0080
+)
+
+func (port *windowsPort) SetMode(mode *Mode) error {
+ params := dcb{}
+ if getCommState(port.handle, &params) != nil {
+ port.Close()
+ return &PortError{code: InvalidSerialPort}
+ }
+ port.setModeParams(mode, &params)
+ if setCommState(port.handle, &params) != nil {
+ port.Close()
+ return &PortError{code: InvalidSerialPort}
+ }
+ return nil
+}
+
+func (port *windowsPort) setModeParams(mode *Mode, params *dcb) {
+ if mode.BaudRate == 0 {
+ params.BaudRate = 9600 // Default to 9600
+ } else {
+ params.BaudRate = uint32(mode.BaudRate)
+ }
+ if mode.DataBits == 0 {
+ params.ByteSize = 8 // Default to 8 bits
+ } else {
+ params.ByteSize = byte(mode.DataBits)
+ }
+ params.StopBits = stopBitsMap[mode.StopBits]
+ params.Parity = parityMap[mode.Parity]
+}
+
+func (port *windowsPort) SetDTR(dtr bool) error {
+ // Like for RTS there are problems with the escapeCommFunction
+ // observed behaviour was that DTR is set from false -> true
+ // when setting RTS from true -> false
+ // 1) Connect -> RTS = true (low) DTR = true (low) OKAY
+ // 2) SetDTR(false) -> RTS = true (low) DTR = false (heigh) OKAY
+ // 3) SetRTS(false) -> RTS = false (heigh) DTR = true (low) ERROR: DTR toggled
+ //
+ // In addition this way the CommState Flags are not updated
+ /*
+ var res bool
+ if dtr {
+ res = escapeCommFunction(port.handle, commFunctionSetDTR)
+ } else {
+ res = escapeCommFunction(port.handle, commFunctionClrDTR)
+ }
+ if !res {
+ return &PortError{}
+ }
+ return nil
+ */
+
+ // The following seems a more reliable way to do it
+
+ params := &dcb{}
+ if err := getCommState(port.handle, params); err != nil {
+ return &PortError{causedBy: err}
+ }
+ params.Flags &= dcbDTRControlDisableMask
+ if dtr {
+ params.Flags |= dcbDTRControlEnable
+ }
+ if err := setCommState(port.handle, params); err != nil {
+ return &PortError{causedBy: err}
+ }
+
+ return nil
+}
+
+func (port *windowsPort) SetRTS(rts bool) error {
+ // It seems that there is a bug in the Windows VCP driver:
+ // it doesn't send USB control message when the RTS bit is
+ // changed, so the following code not always works with
+ // USB-to-serial adapters.
+ //
+ // In addition this way the CommState Flags are not updated
+
+ /*
+ var res bool
+ if rts {
+ res = escapeCommFunction(port.handle, commFunctionSetRTS)
+ } else {
+ res = escapeCommFunction(port.handle, commFunctionClrRTS)
+ }
+ if !res {
+ return &PortError{}
+ }
+ return nil
+ */
+
+ // The following seems a more reliable way to do it
+
+ params := &dcb{}
+ if err := getCommState(port.handle, params); err != nil {
+ return &PortError{causedBy: err}
+ }
+ params.Flags &= dcbRTSControlDisbaleMask
+ if rts {
+ params.Flags |= dcbRTSControlEnable
+ }
+ if err := setCommState(port.handle, params); err != nil {
+ return &PortError{causedBy: err}
+ }
+ return nil
+}
+
+func (port *windowsPort) GetModemStatusBits() (*ModemStatusBits, error) {
+ var bits uint32
+ if !getCommModemStatus(port.handle, &bits) {
+ return nil, &PortError{}
+ }
+ return &ModemStatusBits{
+ CTS: (bits & msCTSOn) != 0,
+ DCD: (bits & msRLSDOn) != 0,
+ DSR: (bits & msDSROn) != 0,
+ RI: (bits & msRingOn) != 0,
+ }, nil
+}
+
+func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
+ commTimeouts := &commTimeouts{
+ ReadIntervalTimeout: 0xFFFFFFFF,
+ ReadTotalTimeoutMultiplier: 0xFFFFFFFF,
+ ReadTotalTimeoutConstant: 0xFFFFFFFE,
+ WriteTotalTimeoutConstant: 0,
+ WriteTotalTimeoutMultiplier: 0,
+ }
+ if timeout != NoTimeout {
+ ms := timeout.Milliseconds()
+ if ms > 0xFFFFFFFE || ms < 0 {
+ return &PortError{code: InvalidTimeoutValue}
+ }
+ commTimeouts.ReadTotalTimeoutConstant = uint32(ms)
+ }
+
+ if err := setCommTimeouts(port.handle, commTimeouts); err != nil {
+ return &PortError{code: InvalidTimeoutValue, causedBy: err}
+ }
+
+ return nil
+}
+
+func (port *windowsPort) Break(d time.Duration) error {
+ if err := setCommBreak(port.handle); err != nil {
+ return &PortError{causedBy: err}
+ }
+
+ time.Sleep(d)
+
+ if err := clearCommBreak(port.handle); err != nil {
+ return &PortError{causedBy: err}
+ }
+
+ return nil
+}
+
+func createOverlappedEvent() (*syscall.Overlapped, error) {
+ h, err := createEvent(nil, true, false, nil)
+ return &syscall.Overlapped{HEvent: h}, err
+}
+
+func nativeOpen(portName string, mode *Mode) (*windowsPort, error) {
+ portName = "\\\\.\\" + portName
+ path, err := syscall.UTF16PtrFromString(portName)
+ if err != nil {
+ return nil, err
+ }
+ handle, err := syscall.CreateFile(
+ path,
+ syscall.GENERIC_READ|syscall.GENERIC_WRITE,
+ 0, nil,
+ syscall.OPEN_EXISTING,
+ syscall.FILE_FLAG_OVERLAPPED,
+ 0)
+ if err != nil {
+ switch err {
+ case syscall.ERROR_ACCESS_DENIED:
+ return nil, &PortError{code: PortBusy}
+ case syscall.ERROR_FILE_NOT_FOUND:
+ return nil, &PortError{code: PortNotFound}
+ }
+ return nil, err
+ }
+ // Create the serial port
+ port := &windowsPort{
+ handle: handle,
+ }
+
+ // Set port parameters
+ params := &dcb{}
+ if getCommState(port.handle, params) != nil {
+ port.Close()
+ return nil, &PortError{code: InvalidSerialPort}
+ }
+ port.setModeParams(mode, params)
+ params.Flags &= dcbDTRControlDisableMask
+ params.Flags &= dcbRTSControlDisbaleMask
+ if mode.InitialStatusBits == nil {
+ params.Flags |= dcbDTRControlEnable
+ params.Flags |= dcbRTSControlEnable
+ } else {
+ if mode.InitialStatusBits.DTR {
+ params.Flags |= dcbDTRControlEnable
+ }
+ if mode.InitialStatusBits.RTS {
+ params.Flags |= dcbRTSControlEnable
+ }
+ }
+ params.Flags &^= dcbOutXCTSFlow
+ params.Flags &^= dcbOutXDSRFlow
+ params.Flags &^= dcbDSRSensitivity
+ params.Flags |= dcbTXContinueOnXOFF
+ params.Flags &^= dcbInX
+ params.Flags &^= dcbOutX
+ params.Flags &^= dcbErrorChar
+ params.Flags &^= dcbNull
+ params.Flags &^= dcbAbortOnError
+ params.XonLim = 2048
+ params.XoffLim = 512
+ params.XonChar = 17 // DC1
+ params.XoffChar = 19 // C3
+ if setCommState(port.handle, params) != nil {
+ port.Close()
+ return nil, &PortError{code: InvalidSerialPort}
+ }
+
+ if port.SetReadTimeout(NoTimeout) != nil {
+ port.Close()
+ return nil, &PortError{code: InvalidSerialPort}
+ }
+ return port, nil
+}