Skip to content

Commit bd34fa1

Browse files
committed
protocol: retry serial Write and Drain on EINTR
On Linux, signals such as SIGWINCH (terminal resize) can interrupt write(2) and tcdrain(3) on serial file descriptors, causing them to return EINTR before any bytes are transferred. The go.bug.st/serial library does not retry internally, so the error bubbles up as "interrupted system call" and aborts the flash. Wrap port.Write and port.Drain in sendCommand with EINTR retry loops so transient signal interruptions are handled transparently. Fixes a "send command 0x12: interrupted system call" failure observed during compressed flash download on ESP32-C3 (USB-JTAG/Serial). Signed-off-by: deadprogram <ron@hybridgroup.com>
1 parent 676e259 commit bd34fa1

1 file changed

Lines changed: 36 additions & 3 deletions

File tree

pkg/espflasher/protocol.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package espflasher
33
import (
44
"bytes"
55
"encoding/binary"
6+
"errors"
67
"fmt"
78
"io"
9+
"syscall"
810
"time"
911

1012
"go.bug.st/serial"
@@ -172,12 +174,12 @@ func (c *conn) sendCommand(opcode byte, data []byte, chk uint32) error {
172174
if end > len(frame) {
173175
end = len(frame)
174176
}
175-
if _, err := c.port.Write(frame[off:end]); err != nil {
177+
if err := writeRetryEINTR(c.port, frame[off:end]); err != nil {
176178
return err
177179
}
178180
}
179181
} else {
180-
if _, err := c.port.Write(frame); err != nil {
182+
if err := writeRetryEINTR(c.port, frame); err != nil {
181183
return err
182184
}
183185
}
@@ -194,7 +196,38 @@ func (c *conn) sendCommand(opcode byte, data []byte, chk uint32) error {
194196
// ensures each frame is committed to the USB-UART bridge before we
195197
// proceed, adding a small but deterministic delay that gives the stub
196198
// more time between consecutive commands.
197-
return c.port.Drain()
199+
return drainRetryEINTR(c.port)
200+
}
201+
202+
// writeRetryEINTR calls port.Write, retrying transparently if interrupted by a
203+
// signal (EINTR). On Linux, signals such as SIGWINCH can interrupt write(2) on
204+
// serial file descriptors before any bytes are transferred.
205+
func writeRetryEINTR(port serial.Port, data []byte) error {
206+
for {
207+
_, err := port.Write(data)
208+
if err == nil {
209+
return nil
210+
}
211+
if errors.Is(err, syscall.EINTR) {
212+
continue
213+
}
214+
return err
215+
}
216+
}
217+
218+
// drainRetryEINTR calls port.Drain, retrying transparently if interrupted by a
219+
// signal (EINTR). On Linux, tcdrain(3) can be interrupted by any signal.
220+
func drainRetryEINTR(port serial.Port) error {
221+
for {
222+
err := port.Drain()
223+
if err == nil {
224+
return nil
225+
}
226+
if errors.Is(err, syscall.EINTR) {
227+
continue
228+
}
229+
return err
230+
}
198231
}
199232

200233
// commandResponse represents a parsed response from the ESP device.

0 commit comments

Comments
 (0)