From fb4b111d503ec814416fa6b4514c8460b2d12b77 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 12 Mar 2025 20:50:56 +0100 Subject: [PATCH 1/2] Hack for CH340 support --- serial_windows.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/serial_windows.go b/serial_windows.go index 5853e51..9ea64b6 100644 --- a/serial_windows.go +++ b/serial_windows.go @@ -275,10 +275,19 @@ func (port *windowsPort) GetModemStatusBits() (*ModemStatusBits, error) { } func (port *windowsPort) SetReadTimeout(timeout time.Duration) error { + // This is a brutal hack to make the CH340 chipset work properly. + // Normally this value should be 0xFFFFFFFE but, after a lot of + // tinkering, I discovered that any value with the highest + // bit set will make the CH340 driver behave like the timeout is 0, + // in the best cases leading to a spinning loop... + // (could this be a wrong signed vs unsigned conversion in the driver?) + // https://github.com/arduino/serial-monitor/issues/112 + const MaxReadTotalTimeoutConstant = 0x7FFFFFFE + commTimeouts := &windows.CommTimeouts{ ReadIntervalTimeout: 0xFFFFFFFF, ReadTotalTimeoutMultiplier: 0xFFFFFFFF, - ReadTotalTimeoutConstant: 0xFFFFFFFE, + ReadTotalTimeoutConstant: MaxReadTotalTimeoutConstant, WriteTotalTimeoutConstant: 0, WriteTotalTimeoutMultiplier: 0, } @@ -287,6 +296,11 @@ func (port *windowsPort) SetReadTimeout(timeout time.Duration) error { if ms > 0xFFFFFFFE || ms < 0 { return &PortError{code: InvalidTimeoutValue} } + + if ms > MaxReadTotalTimeoutConstant { + ms = MaxReadTotalTimeoutConstant + } + commTimeouts.ReadTotalTimeoutConstant = uint32(ms) } From 1ff9b6fa9a7c0c0dd0f0dc5c25e2902914704a19 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 12 Mar 2025 20:52:40 +0100 Subject: [PATCH 2/2] If the timeout is set to NoTimeout, make Read wait forever. Otherwise the maximul allowed timeout is 0x7FFFFFFF milliseconds, equivalent to about 24.85 days. This patch will make a transparent repeated read in case this long timeout should ever happen. --- serial_windows.go | 53 ++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/serial_windows.go b/serial_windows.go index 9ea64b6..37b3b39 100644 --- a/serial_windows.go +++ b/serial_windows.go @@ -28,8 +28,9 @@ import ( ) type windowsPort struct { - mu sync.Mutex - handle windows.Handle + mu sync.Mutex + handle windows.Handle + hasTimeout bool } func nativeGetPortsList() ([]string, error) { @@ -72,26 +73,33 @@ func (port *windowsPort) Read(p []byte) (int, error) { } defer windows.CloseHandle(ev.HEvent) - err = windows.ReadFile(port.handle, p, &readed, ev) - if err == windows.ERROR_IO_PENDING { - err = windows.GetOverlappedResult(port.handle, ev, &readed, true) - } - switch err { - case nil: - // operation completed successfully - case windows.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 - } + for { + err = windows.ReadFile(port.handle, p, &readed, ev) + if err == windows.ERROR_IO_PENDING { + err = windows.GetOverlappedResult(port.handle, ev, &readed, true) + } + switch err { + case nil: + // operation completed successfully + case windows.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 + // Timeout + port.mu.Lock() + hasTimeout := port.hasTimeout + port.mu.Unlock() + if hasTimeout { + return 0, nil + } + } } func (port *windowsPort) Write(p []byte) (int, error) { @@ -304,9 +312,12 @@ func (port *windowsPort) SetReadTimeout(timeout time.Duration) error { commTimeouts.ReadTotalTimeoutConstant = uint32(ms) } + port.mu.Lock() + defer port.mu.Unlock() if err := windows.SetCommTimeouts(port.handle, commTimeouts); err != nil { return &PortError{code: InvalidTimeoutValue, causedBy: err} } + port.hasTimeout = (timeout != NoTimeout) return nil }