Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update UART driver with specs, documentaion, and read/2 #1508

Open
wants to merge 2 commits into
base: release-0.6
Choose a base branch
from

Conversation

UncleGrumpy
Copy link
Collaborator

Updates the UART driver to include function specs and documentation.
Adds new uart:read/2 with a timeout parameter.

Closes #1446

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later

Copy link
Contributor

@petermm petermm left a comment

Choose a reason for hiding this comment

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

works, great! even when doing hundreds of uart:read per second!

libs/eavmlib/src/uart.erl Show resolved Hide resolved
src/platforms/esp32/components/avm_builtins/uart_driver.c Outdated Show resolved Hide resolved
src/platforms/esp32/components/avm_builtins/uart_driver.c Outdated Show resolved Hide resolved
src/platforms/esp32/components/avm_builtins/uart_driver.c Outdated Show resolved Hide resolved
@UncleGrumpy
Copy link
Collaborator Author

UncleGrumpy commented Feb 3, 2025

I pushed changes that eliminate the use of any timer or task in the C code, and instead added a cancel_read command, handling the timeout and sending the cancel command from erlang, following the suggestion @pguyot made.

I believe this is much more efficient, and less resource intensive than creating a timer.

@UncleGrumpy UncleGrumpy requested a review from pguyot February 3, 2025 01:02
Copy link
Collaborator

@pguyot pguyot left a comment

Choose a reason for hiding this comment

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

Thank you for this new version! Besides the little bug on timeout result, could you please fix potential concurrency issues?

case port:call(Pid, read, Timeout) of
{error, timeout} ->
port:call(Pid, cancel_read),
timeout;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you mean {error, timeout} here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was not convinced that the return should be {error, timeout} for read/2, since timeout is an expected result... we either want data or a timeout. I am not opposed to making this an error, but it seemed a bit wrong to me.

Copy link
Contributor

@petermm petermm Feb 3, 2025

Choose a reason for hiding this comment

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

true, I prefer the error tuple though.

having a single atom returns leads to more defensive code needed imho.

eg. there is an additional return type, that I need to handle.

And it might even catch me out by surprise, "shipped to prod" etc.

    case :uart.read(port, 0) do
      {:ok, read} ->
       #
      {:error, error} ->
        # 
      other ->
        # 
    end

Copy link
Collaborator Author

@UncleGrumpy UncleGrumpy Feb 3, 2025

Choose a reason for hiding this comment

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

Sounds good to me, just looking for the return "of least surprise". I will update the return and documentation.

It does make the most sense to have read/1 and read/2 have the same returns.

This also lets me make the return specifically a tuple in the spec, to make for more accurate use of dialyzer.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was also reacting to the spec that doesn't allow timeout.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Clearly I was on the fence about the proper return there. lol. It's fixed now.

{
struct UARTData *uart_data = ctx->platform_data;

uart_data->reader_process_pid = term_invalid_term();
Copy link
Collaborator

Choose a reason for hiding this comment

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

uart_interrupt_callback can run on the other scheduler on dual core esp32s. So let's take advantage of this PR to protect uart_data accesses with a mutex or Atomic fields if it's sufficient.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think making the two fields uart_data->reader_process_pid, and uart_data->reader_ref_ticks atomic should mitigate the problem, since these are both touched during read, cancel_read, and the interrupt callback.

@petermm
Copy link
Contributor

petermm commented Feb 3, 2025

Works great still.

nitpick:
Should it only take a positive integer as timeout value? pos_integer()

Tried :uart.read(port, 0) but it would never get a read, of course also a nonsensical value..

@UncleGrumpy
Copy link
Collaborator Author

Thank you for this new version! Besides the little bug on timeout result, could you please fix potential concurrency issues?

Yes. I see what you mean now, I thought your comment about concurrency was related to the previous implementation, but I see now that you were talking about pre-existing concurrency problems.

@pguyot
Copy link
Collaborator

pguyot commented Feb 3, 2025

Yes. I see what you mean now, I thought your comment about concurrency was related to the previous implementation, but I see now that you were talking about pre-existing concurrency problems.

Exactly. And issues could be amplified by the new code.

Add type definitions for input parameters.
Add spec for all exported functions.
Add documentation for functions.

Signed-off-by: Winford <winford@object.stream>
Adds a new uart:read/2 that takes a timeout parameter for reads. If no data is received during the
timeout period `timeout` is returned, and the listener is removed allowing for another read without
getting the `{error, ealready}` error tuple.

Closes atomvm#1446

Signed-off-by: Winford <winford@object.stream>
term reader_process_pid;
uint64_t reader_ref_ticks;
_Atomic term reader_process_pid;
_Atomic uint64_t reader_ref_ticks;
Copy link
Collaborator Author

@UncleGrumpy UncleGrumpy Feb 3, 2025

Choose a reason for hiding this comment

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

Now I have some doubts about this fix, it seems like the reader_process_pid and reader_ref_ticks could get out of sync, if a process were to change the reader_process_id, while another running on a different core was waiting to change it, and the first process was interrupted by a higher priority task (before changing reader_ref_ticks), and the second then was allowed to change reader_process_id and then reader_ref_ticks, before the first process was scheduled to resume. When the first process is scheduled again it would continue on to change the reader_ref_ticks.

I think, instead, we need to enter a critical section before updating both values.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Or both?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants