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

cpu/sam0_common/periph/uart: implement non-blocking write #12064

Merged
merged 2 commits into from
Nov 28, 2019

Conversation

benpicco
Copy link
Contributor

@benpicco benpicco commented Aug 22, 2019

Contribution description

This replaces the polling based UART TX implementation on the sam0 platform with an interrupt based one.
Thread-safe Ringbuffer is used for the TX buffer with a compile-time configurable buffer size. (May be tweaked.)

This violates the specification of uart_write() as this makes uart_write() non-blocking.

The non-blocking mode is now only used if the module periph_uart_nonblocking is used.

I wanted to have a non-blocking debug output when debugging a race condition, so I implemented this. Maybe a non-blocking uart_write could be generally useful though.

Testing procedure

add USEMODULE += periph_uart_nonblocking to your application.

Everything should behave as before, although the console output feels more snappy.
Only the shell / debug output have been tested.

Issues/PRs references

none

@benpicco benpicco added Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation Discussion: RFC The issue/PR is used as a discussion starting point about the item of the issue/PR Area: drivers Area: Device drivers labels Aug 22, 2019
@jcarrano
Copy link
Contributor

I experimented with this a while ago, but never PR'd it. Here is my branch:

jcarrano@6e7e64a

I defined a new function uart_write_async which blocks only if there is data waiting to be sent, otherwise it queues the current data and returns. I used a zero-copy approach by just storing a pointer to the buffer and then I redefined uart_write in terms of uart_write_async.

@benpicco
Copy link
Contributor Author

I don't think zero-copy is the right approach for a general uart_write function though, e.g. you can't do zero-copy when using printf.

@dylad
Copy link
Member

dylad commented Aug 28, 2019

Isn't it better to create a dedicated pseudomodule for this ? the ROM cost looks heavy otherwise.
I can have a deeper look and run some test after the summit.

@jcarrano
Copy link
Contributor

the ROM cost looks heavy otherwise.

Is there any application that stops fitting because of this? The cost of making it optional (at leat in the way it is done here and in most of riot) is a considerable increase in complexity and a decrease in readability, with all the ifdefs.

Add to that the fact that now you have two code paths in the uart driver that you have to test.

@benpicco benpicco changed the title [RFC] cpu/sam0_common/periph/uart: implement non-blocking write cpu/sam0_common/periph/uart: implement non-blocking write Sep 23, 2019
@keestux
Copy link
Contributor

keestux commented Sep 23, 2019

Is this a normal (allowed?) way of defining a regular macro?

USEMODULE += periph_uart_nonblocking

In most cases when I see #ifdef MODULE_blabla it means "if module blabla is present". But in this case, periph_uart_nonblocking is not a module. It is a macro to select a certain behaviour of the uart module.

Shouldn't we just have a macro (defined through CFLAGS) with a name such as PERIPH_UART_NONBLOCKING?

@keestux
Copy link
Contributor

keestux commented Sep 26, 2019

Notice that there is something called PSEUDOMODULES. I think makes sense to add periph_uart_nonblocking in makefiles/pseudomodules.inc.mk. No?

@benpicco
Copy link
Contributor Author

There is already

PSEUDOMODULES += periph_%

in pseudomodules.inc.mk - this also matches periph_uart_nonblocking.

@benpicco benpicco added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Sep 26, 2019
@keestux
Copy link
Contributor

keestux commented Sep 26, 2019

@benpicco Yes, you're right. So it's not a problem.

@benpicco benpicco removed the Discussion: RFC The issue/PR is used as a discussion starting point about the item of the issue/PR label Oct 30, 2019
@benpicco benpicco force-pushed the sam0-buffered_uart branch 2 times, most recently from 8f61d99 to fb96555 Compare October 30, 2019 15:18
@benpicco
Copy link
Contributor Author

I took the liberty to squash because it's more convenient being able to just grab the patch when debugging race conditions.

Copy link
Member

@dylad dylad left a comment

Choose a reason for hiding this comment

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

any reason to keep SAM D&L 21 away from these changes ?

Makefile.dep Outdated
ifneq (,$(filter periph_uart_nonblocking, $(USEMODULE)))
USEMODULE += periph_uart
endif

Copy link
Member

Choose a reason for hiding this comment

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

Why is this needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So everything that depends on periph_uart is also fulfilled with periph_uart_nonblocking

Copy link
Member

Choose a reason for hiding this comment

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

But should it belongs to Makefile.dep ? Why not cpu/sam0_common/Makefile.dep ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So other platforms can implement it too.

@benpicco
Copy link
Contributor Author

benpicco commented Nov 13, 2019

any reason to keep SAM D&L 21 away from these changes ?

I also tested this on samr21-xpro. Only saml1x and samd5x have a separate TXE IRQ.

Copy link
Contributor

@aabadie aabadie left a comment

Choose a reason for hiding this comment

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

I've looked quickly at this PR. I have no comment on the implementation of the non blocking uart mode in sam0 (I'm not an expert on these platforms) but I think the integration in the build system needs a complete rework:

  • add FEATURES_PROVIDED += periph_uart_nonblocking to cpu/sam0_common/Makefile.features
  • in Makefile.dep, just add:
    ifneq (,$(filter periph_uart_nonblocking,$(USEMODULE)))
      FEATURES_REQUIRED += periph_uart
    endif
    instead of the current version (See how this is already done for periph_gpio/periph_gpio_irq in Makefile.dep)
  • Remove all uses and definition of the USE_NONBLOCKING macro and just replace the uses of #if USE_NONBLOCKING by #ifdef MODULE_PERIPH_UART_NONBLOCKING
  • Remove all cpu/samxx/Makefile.dep introduced by this PR: they are empty and thus useless.
  • add a test application with:
    FEATURES_REQUIRED += periph_uart periph_uart_nonblocking
    in its makefile. This application will print some data and this should work. A Python test script could be added for this purpose.
    Note that, thanks to the use FEATURES_REQUIRED, this application will only be built for sam0 based boards as only sam0 provides this feature (for the moment). The good thing it that it will ensure that the CI is building the nonblocking related code.

I'm blocking this PR until the mentioned problems are fixed and a dedicated test application is added.

@benpicco
Copy link
Contributor Author

Remove all cpu/samxx/Makefile.dep introduced by this PR: they are empty and thus useless.

They are all but useless. This implementation depends on tsrb, this dependency is resolved in sam0_common/Makefile.dep so the sam0-siblings have to include it.

I've addressed all the other comments.
With the test application I get

== printed in 2100735/2100000 µs ==

with periph_uart_nonblocking and

== printed in 2152407/2100000 µs ==

with periph_uart.

@aabadie
Copy link
Contributor

aabadie commented Nov 15, 2019

They are all but useless.

You are right. I don't know why I thought they were initially empty.

Thanks for updating following the other suggestions. Just one is still missing: add a Python script for automatic testing.

@benpicco
Copy link
Contributor Author

I added a small Python script.
When using periph_uart instead of periph_uart_nonblocking the test does fail as expected.

@aabadie aabadie dismissed their stale review November 25, 2019 06:46

My comments are addressed.

@aabadie
Copy link
Contributor

aabadie commented Nov 25, 2019

@dylad, can you have a look at this one when you have time ? I'm fine with the test application and automatic test script. The build system integration is also ok for me.
Just the important part, e.g the non blocking uart code, needs a review now :)

Copy link
Member

@dylad dylad left a comment

Choose a reason for hiding this comment

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

SAML21 isn't happy. See comment below.
Can someone doublecheck on SAMD21 just to be sure ?

NVIC_EnableIRQ(SERCOM0_2_IRQn + (sercom_id(dev(uart)) * 4));
#else
/* enable UART ISR */
NVIC_EnableIRQ(SERCOM0_IRQn + sercom_id(dev(uart)));
Copy link
Member

@dylad dylad Nov 25, 2019

Choose a reason for hiding this comment

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

Got a hard time debugging this one.
In the current shape the provided test app doesn't work at all on SAML21.
No DRE interrupt is triggered. In fact, the NVIC_EnableIRQ is never call because there is no callback provided so we did not enter here.
The callback is not provided because weren't using a shell.
So we need a second way to enable the NVIC in this specific case (TX only, no RX).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea you are right.
I only tested the non-callback case on samd5x, for samd2x/saml2x the UART interrupt was only enabled when a callback was provided.
It is now also enabled when MODULE_PERIPH_UART_NONBLOCKING is defined.

NVIC_EnableIRQ(SERCOM0_0_IRQn + (sercom_id(dev(uart)) * 4));
#else
/* enable UART ISR */
NVIC_EnableIRQ(SERCOM0_IRQn + sercom_id(dev(uart)));
Copy link
Member

Choose a reason for hiding this comment

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

In some case, the NVIC_EnabeIRQ is called twice, this might be confusing.
I would rather go to something like :

    if ((rx_cb) && (uart_config[uart].rx_pin != GPIO_UNDEF)) {
        uart_ctx[uart].rx_cb = rx_cb;
        uart_ctx[uart].arg = arg;
#ifdef UART_HAS_TX_ISR
        /* enable RXNE ISR */
        NVIC_EnableIRQ(SERCOM0_2_IRQn + (sercom_id(dev(uart)) * 4));
#else
        /* enable UART ISR */
        NVIC_EnableIRQ(SERCOM0_IRQn + sercom_id(dev(uart)));
#endif /* UART_HAS_TX_ISR */
        dev(uart)->CTRLB.reg |= SERCOM_USART_CTRLB_RXEN;
        dev(uart)->INTENSET.reg |= SERCOM_USART_INTENSET_RXC;
        /* set wakeup receive from sleep if enabled */
        if (uart_config[uart].flags & UART_FLAG_WAKEUP) {
            dev(uart)->CTRLB.reg |= SERCOM_USART_CTRLB_SFDE;
        }
    }
#if defined(MODULE_PERIPH_UART_NONBLOCKING)
else {
    NVIC_EnableIRQ(SERCOM0_IRQn + sercom_id(dev(uart)));
}

#if defined(UART_HAS_TX_ISR) 
    /* enable TXE ISR */
    NVIC_EnableIRQ(SERCOM0_0_IRQn + (sercom_id(dev(uart)) * 4));
#endif
#endif /* MODULE_PERIPH_UART_NONBLOCKING */

@dylad
Copy link
Member

dylad commented Nov 26, 2019

This now works on SAML21 but I tested on SAML10 and SAML11 and here is result WITH uart_nonblocking:

2019-11-26 08:54:20,067 # m�ain(): This is RIOT! (Version: 2020.01-devel-426-ge36b5-sam0-buffe
red_uart)
2019-11-26 08:54:20,068 # 
2019-11-26 08:54:20,166 # J�oin us now and share the software;
2019-11-26 08:54:20,268 # Y�ou'll be free, hackers, you'll be free.
2019-11-26 08:54:20,368 # J�oin us now and share the software;
2019-11-26 08:54:20,470 # Y�ou'll be free, hackers, you'll be free.
2019-11-26 08:54:20,568 # 
2019-11-26 08:54:20,671 # �H�oarders can get piles of money,
2019-11-26 08:54:20,773 # T�hat is true, hackers, that is true.
2019-11-26 08:54:20,874 # B�ut they cannot help their neighbors;
2019-11-26 08:54:20,976 # T�hat's not good, hackers, that's not good.
2019-11-26 08:54:21,074 # 
2019-11-26 08:54:21,177 # �W�hen we have enough free software
2019-11-26 08:54:21,279 # A�t our call, hackers, at our call,
2019-11-26 08:54:21,380 # W�e'll kick out those dirty licenses
2019-11-26 08:54:21,481 # E�ver more, hackers, ever more.
2019-11-26 08:54:21,580 # 
2019-11-26 08:54:21,684 # �J�oin us now and share the software;
2019-11-26 08:54:21,785 # Y�ou'll be free, hackers, you'll be free.
2019-11-26 08:54:21,886 # J�oin us now and share the software;
2019-11-26 08:54:21,988 # Y�ou'll be free, hackers, you'll be free.
2019-11-26 08:54:22,085 # 
2019-11-26 08:54:22,190 # �=�= printed in 2105365/2100000 µs ==
2019-11-26 08:54:22,191 # [SUCCESS]

The output is fine without the uart_nonblocking module. I'll try to investigate further.

@dylad
Copy link
Member

dylad commented Nov 26, 2019

One really quick&dirty workaround is the following:

#ifdef MODULE_PERIPH_UART_NONBLOCKING
static inline void irq_handler_tx(unsigned uartnum)
{
    if(!tsrb_avail(&uart_tx_rb[uartnum])) {
        return;
    }
    dev(uartnum)->DATA.reg = tsrb_get_one(&uart_tx_rb[uartnum]);
    /* disable the interrupt if there are no more bytes to send */
    if (tsrb_empty(&uart_tx_rb[uartnum])) {
        dev(uartnum)->INTENCLR.reg = SERCOM_USART_INTENSET_DRE;
    }
}
#endif

But I'd like to find a better patch...

Copy link
Member

@dylad dylad left a comment

Choose a reason for hiding this comment

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

Tested work on SAMR21 (Iotlab), SAML10, SAML11, SAML21, SAME54.
Please squash !

@dylad dylad added this to the Release 2020.01 milestone Nov 27, 2019
Implement interrupt based uart_write() using a tsrb for the TX buffer.

To enable it, add

    USEMODULE += periph_uart_nonblocking

to your Makefile.
@dylad
Copy link
Member

dylad commented Nov 28, 2019

static tests failed, you can amend directly

The application is mainly to compile-test non-blocking UART
functionality, but some functional testing is also possible.

With non-blocking UART the total runtime of the program is 2100735 µs
on same54-xpro.
With blocking UART the total runtime is 2152407 µs.
@dylad
Copy link
Member

dylad commented Nov 28, 2019

Here we go ! Thanks for the PR @benpicco !

@dylad dylad merged commit 6a4259e into RIOT-OS:master Nov 28, 2019
@benpicco benpicco deleted the sam0-buffered_uart branch November 28, 2019 09:09
@benpicco
Copy link
Contributor Author

Thank you for the review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: drivers Area: Device drivers CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants