Skip to content

drivers: i2s: honour the configured timeout in i2s_buf_write() - #115727

Merged
nashif merged 2 commits into
zephyrproject-rtos:mainfrom
thc1006:fix/i2s-buf-write-honor-timeout
Aug 22, 2026
Merged

drivers: i2s: honour the configured timeout in i2s_buf_write()#115727
nashif merged 2 commits into
zephyrproject-rtos:mainfrom
thc1006:fix/i2s-buf-write-honor-timeout

Conversation

@thc1006

@thc1006 thc1006 commented Aug 9, 2026

Copy link
Copy Markdown
Member

i2s_buf_write() acquires its memory block with K_FOREVER, so on a multithreaded build neither error code it documents for that step can be produced: -ENOMEM because K_FOREVER does not return it, which also leaves the if (ret < 0) below the call as dead code, and -EAGAIN because nothing ever times out. An application that exhausts the TX slab blocks forever instead of being told. Without CONFIG_MULTITHREADING the slab treats every timeout as no-wait, so only -EAGAIN is unreachable there.

Passing the configured timeout through fixes both, and k_mem_slab_alloc() already returns exactly the two documented codes, so the errno goes back unchanged rather than being flattened to -ENOMEM.

Fixes #113333

Two stages, not one deadline

Drivers already apply the same configured timeout when enqueueing the block, so after this change the call can spend it twice, once acquiring and once enqueueing. Making it a single end-to-end deadline would need a way to pass the remaining time into i2s_write(), and the API has no room for that today, so I documented it as a per-stage bound rather than quietly leave the impression it is one.

That also means no single timeout value reproduces the old combination of an unbounded acquisition and a bounded enqueue, which is what the migration note now says.

A related problem I am deliberately keeping out of this PR

Several drivers pass i2s_config.timeout straight into K_MSEC(). Z_TIMEOUT_MS() is MAX(t, 0), so SYS_FOREVER_MS becomes K_NO_WAIT there, and the field documents SYS_FOREVER_MS as a valid value. Four drivers do this; the twenty other call sites in the subsystem already convert with SYS_TIMEOUT_MS(). It predates this change and is a driver fix rather than an API one, so I filed it separately as #116030.

Testing

The two new tests queue NUM_TX_BLOCKS blocks without starting the stream, which leaves every one of them owned by the driver, then check that the next call reports -ENOMEM with a timeout of 0 and -EAGAIN with a finite one. Both fail before the driver's write(), so no loopback fixture is involved. Draining through i2s_buf_write() rather than the slab API keeps this usable from a user thread, since k_mem_slab_alloc() is not a syscall, so one helper covers the supervisor and userspace copies of the allocation.

Built for esp32s3_devkitc/esp32s3/procpu. I have not run the suite on hardware; it needs the loopback fixture.

@sylvioalves

Copy link
Copy Markdown
Contributor

Please check z_vrfy_i2s_buf_write() in i2s_handlers.c as it would also need the same timeout.

Comment thread drivers/i2s/i2s_common.c Outdated
*/
ret = k_mem_slab_alloc(tx_cfg->mem_slab, &mem_block,
tx_cfg->timeout == SYS_FOREVER_MS ? K_FOREVER
: K_MSEC(tx_cfg->timeout));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
: K_MSEC(tx_cfg->timeout));
: SYS_TIMEOUT_MS(tx_cfg->timeout));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took it, and a bit further: SYS_TIMEOUT_MS maps SYS_FOREVER_MS itself, so the whole ternary goes.

Comment thread drivers/i2s/i2s_common.c Outdated
@sylvioalves

Copy link
Copy Markdown
Contributor

Would you also add an entry in the migration-guide file that i2s_buf_write() API now honors the timeout properly?

@thc1006

thc1006 commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

Good catch on the verifier. It had the same K_FOREVER and was also flattening the result to -ENOMEM, so both now use SYS_TIMEOUT_MS and return what the slab gives. Migration-guide entry added under a new I2S section.

i2s_buf_write() acquires its memory block with K_FOREVER, so on a
multithreaded build the two error codes it documents for that step can
never be produced: -ENOMEM because K_FOREVER does not return it, which
also makes the error branch below the call dead code, and -EAGAIN
because nothing ever times out. An application that exhausts the slab
blocks forever instead of being told. Without CONFIG_MULTITHREADING the
slab treats every timeout as no-wait, so only -EAGAIN is unreachable.

The userspace path has its own copy. z_vrfy_i2s_buf_write() allocates
the same way and then flattens whatever it gets to -ENOMEM, which was
harmless only because that branch was equally unreachable. Both copies
now use SYS_TIMEOUT_MS(), which already maps SYS_FOREVER_MS itself, and
both return the slab's errno unchanged, so the two paths agree.

k_mem_slab_alloc() produces exactly the two documented codes: -ENOMEM
when told not to wait, which is what a configured timeout of 0 becomes,
and -EAGAIN when the wait expires.

One consequence is worth stating rather than leaving to be discovered:
drivers already apply the same configured timeout when enqueueing the
block, so this call can now spend it twice, once acquiring and once
enqueueing. Making it a single end-to-end deadline would need a way to
pass the remaining time into i2s_write(), which the API has no room for
today, so it is documented as a per-stage bound instead.

The in-tree suites are unaffected: the loopback test prefills exactly
NUM_TX_BLOCKS blocks into a slab of NUM_TX_BLOCKS, so every acquisition
succeeds without waiting.

Fixes zephyrproject-rtos#113333

Signed-off-by: Hsiu-Chi Tsai <hctsai@linux.com>
@thc1006
thc1006 force-pushed the fix/i2s-buf-write-honor-timeout branch from 6cf7404 to b8a7fa7 Compare August 12, 2026 10:27
@zephyrbot zephyrbot added the area: Tests Issues related to a particular existing or missing test label Aug 12, 2026
Comment thread tests/drivers/i2s/i2s_api/src/test_i2s_errors.c Outdated
The acquisition now reports an exhausted slab instead of waiting on it,
and nothing covered either code it can return. Queue NUM_TX_BLOCKS
blocks without starting the stream, which leaves them all owned by the
driver, then check that the next call reports -ENOMEM with a timeout of
0 and -EAGAIN with a finite one.

Both cases fail before the driver's write(), so no loopback fixture is
involved. Draining the slab through i2s_buf_write() rather than the
slab API keeps this usable from a user thread, so the same helper can
cover the supervisor and userspace copies of the allocation.

Signed-off-by: Hsiu-Chi Tsai <hctsai@linux.com>
@thc1006
thc1006 force-pushed the fix/i2s-buf-write-honor-timeout branch from b8a7fa7 to 23c35e8 Compare August 12, 2026 11:52
@sonarqubecloud

Copy link
Copy Markdown

@henrikbrixandersen henrikbrixandersen removed their assignment Aug 12, 2026
@nashif
nashif merged commit b57f6af into zephyrproject-rtos:main Aug 22, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: I2S area: Tests Issues related to a particular existing or missing test Release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

drivers: i2s: i2s_buf_write() documents -EAGAIN/-ENOMEM but allocates with K_FOREVER, so an exhausted TX slab is a silent unkillable hang

6 participants