Skip to content

Commit d840971

Browse files
authored
machine: [rp2] discount scheduling delays in I2C timeouts (#4876)
The `gosched` call introduce arbitrary long delays in general, and in TinyGo particular because the goroutine scheduler is cooperative and doesn't preempt busy (e.g. compute-heavy) goroutines. Before this change, the timeout logic would read, simplified: deadline := now() + timeout startTX() for !txDone() { if now() > deadline { return timeoutError } gosched() // (1) } startRx() // (2) for !rxDone() { // (3) if now() > deadline { return timeoutError } gosched() } What could happen in a busy system is: - The gosched marked (1) would push now() to be > than deadline. - startRx is called (2), but the call to rxDone immediately after would report it not yet done. - The check marked (3) would fail, even though only a miniscule amount of time has passed between startRx and the check. This change ensures that the timeout clock discounts time spent in `gosched`. The logic now reads, around every call to `gosched`: deadline := now() + timeout startTX() for !txDone() { if now() > deadline { return timeoutError } before := now() gosched() deadline += now() - before } I tested this change by simulating a busy goroutine: go func() { for { // Busy. before := time.Now() for time.Since(before) < 100*time.Millisecond { } // Sleep. time.Sleep(100 * time.Millisecond) } }() and testing that I2C transfers would no longer time out.
1 parent ba3b3e8 commit d840971

File tree

1 file changed

+13
-7
lines changed

1 file changed

+13
-7
lines changed

src/machine/machine_rp2_i2c.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
8686
if i2c.mode != I2CModeController {
8787
return ErrI2CWrongMode
8888
}
89-
90-
// timeout in microseconds.
91-
const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system.
92-
return i2c.tx(uint8(addr), w, r, timeout)
89+
return i2c.tx(uint8(addr), w, r)
9390
}
9491

9592
// Listen starts listening for I2C requests sent to specified address
@@ -218,8 +215,8 @@ func (i2c *I2C) enable() {
218215
//
219216
//go:inline
220217
func (i2c *I2C) disable() error {
221-
const MAX_T_POLL_COUNT = 64 // 64 us timeout corresponds to around 1000kb/s i2c transfer rate.
222-
deadline := ticks() + MAX_T_POLL_COUNT
218+
const timeout_us = 4_000
219+
deadline := ticks() + timeout_us
223220
i2c.Bus.IC_ENABLE.Set(0)
224221
for i2c.Bus.IC_ENABLE_STATUS.Get()&1 != 0 {
225222
if ticks() > deadline {
@@ -285,7 +282,8 @@ func (i2c *I2C) deinit() (resetVal uint32) {
285282
}
286283

287284
// tx performs blocking write followed by read to I2C bus.
288-
func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
285+
func (i2c *I2C) tx(addr uint8, tx, rx []byte) (err error) {
286+
const timeout_us = 4_000
289287
deadline := ticks() + timeout_us
290288
if addr >= 0x80 || isReservedI2CAddr(addr) {
291289
return ErrInvalidTgtAddr
@@ -338,7 +336,9 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
338336
return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
339337
}
340338

339+
before := ticks()
341340
gosched()
341+
deadline += ticks() - before
342342
}
343343

344344
abortReason = i2c.getAbortReason()
@@ -361,7 +361,9 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
361361
return errI2CWriteTimeout
362362
}
363363

364+
before := ticks()
364365
gosched()
366+
deadline += ticks() - before
365367
}
366368
i2c.Bus.IC_CLR_STOP_DET.Get()
367369
}
@@ -382,7 +384,9 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
382384
first := rxCtr == 0
383385
last := rxCtr == rxlen-1
384386
for i2c.writeAvailable() == 0 {
387+
before := ticks()
385388
gosched()
389+
deadline += ticks() - before
386390
}
387391
i2c.Bus.IC_DATA_CMD.Set(
388392
boolToBit(first && rxStart)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
@@ -399,7 +403,9 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
399403
return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
400404
}
401405

406+
before := ticks()
402407
gosched()
408+
deadline += ticks() - before
403409
}
404410
if abort {
405411
break

0 commit comments

Comments
 (0)