mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-16 10:43:29 +00:00
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.
This commit is contained in:
@@ -86,10 +86,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
|
|||||||
if i2c.mode != I2CModeController {
|
if i2c.mode != I2CModeController {
|
||||||
return ErrI2CWrongMode
|
return ErrI2CWrongMode
|
||||||
}
|
}
|
||||||
|
return i2c.tx(uint8(addr), w, r)
|
||||||
// timeout in microseconds.
|
|
||||||
const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system.
|
|
||||||
return i2c.tx(uint8(addr), w, r, timeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen starts listening for I2C requests sent to specified address
|
// Listen starts listening for I2C requests sent to specified address
|
||||||
@@ -218,8 +215,8 @@ func (i2c *I2C) enable() {
|
|||||||
//
|
//
|
||||||
//go:inline
|
//go:inline
|
||||||
func (i2c *I2C) disable() error {
|
func (i2c *I2C) disable() error {
|
||||||
const MAX_T_POLL_COUNT = 64 // 64 us timeout corresponds to around 1000kb/s i2c transfer rate.
|
const timeout_us = 4_000
|
||||||
deadline := ticks() + MAX_T_POLL_COUNT
|
deadline := ticks() + timeout_us
|
||||||
i2c.Bus.IC_ENABLE.Set(0)
|
i2c.Bus.IC_ENABLE.Set(0)
|
||||||
for i2c.Bus.IC_ENABLE_STATUS.Get()&1 != 0 {
|
for i2c.Bus.IC_ENABLE_STATUS.Get()&1 != 0 {
|
||||||
if ticks() > deadline {
|
if ticks() > deadline {
|
||||||
@@ -285,7 +282,8 @@ func (i2c *I2C) deinit() (resetVal uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tx performs blocking write followed by read to I2C bus.
|
// tx performs blocking write followed by read to I2C bus.
|
||||||
func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
func (i2c *I2C) tx(addr uint8, tx, rx []byte) (err error) {
|
||||||
|
const timeout_us = 4_000
|
||||||
deadline := ticks() + timeout_us
|
deadline := ticks() + timeout_us
|
||||||
if addr >= 0x80 || isReservedI2CAddr(addr) {
|
if addr >= 0x80 || isReservedI2CAddr(addr) {
|
||||||
return ErrInvalidTgtAddr
|
return ErrInvalidTgtAddr
|
||||||
@@ -338,7 +336,9 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
|||||||
return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
|
return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
before := ticks()
|
||||||
gosched()
|
gosched()
|
||||||
|
deadline += ticks() - before
|
||||||
}
|
}
|
||||||
|
|
||||||
abortReason = i2c.getAbortReason()
|
abortReason = i2c.getAbortReason()
|
||||||
@@ -361,7 +361,9 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
|||||||
return errI2CWriteTimeout
|
return errI2CWriteTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
before := ticks()
|
||||||
gosched()
|
gosched()
|
||||||
|
deadline += ticks() - before
|
||||||
}
|
}
|
||||||
i2c.Bus.IC_CLR_STOP_DET.Get()
|
i2c.Bus.IC_CLR_STOP_DET.Get()
|
||||||
}
|
}
|
||||||
@@ -382,7 +384,9 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
|||||||
first := rxCtr == 0
|
first := rxCtr == 0
|
||||||
last := rxCtr == rxlen-1
|
last := rxCtr == rxlen-1
|
||||||
for i2c.writeAvailable() == 0 {
|
for i2c.writeAvailable() == 0 {
|
||||||
|
before := ticks()
|
||||||
gosched()
|
gosched()
|
||||||
|
deadline += ticks() - before
|
||||||
}
|
}
|
||||||
i2c.Bus.IC_DATA_CMD.Set(
|
i2c.Bus.IC_DATA_CMD.Set(
|
||||||
boolToBit(first && rxStart)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
|
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) {
|
|||||||
return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
|
return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
before := ticks()
|
||||||
gosched()
|
gosched()
|
||||||
|
deadline += ticks() - before
|
||||||
}
|
}
|
||||||
if abort {
|
if abort {
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user