Skip to content

boot: Moved stdio_init() into periph_init()#10806

Closed
maribu wants to merge 7 commits intoRIOT-OS:masterfrom
maribu:early_stdio
Closed

boot: Moved stdio_init() into periph_init()#10806
maribu wants to merge 7 commits intoRIOT-OS:masterfrom
maribu:early_stdio

Conversation

@maribu
Copy link
Copy Markdown
Member

@maribu maribu commented Jan 17, 2019

Contribution description

Prior to this commit stdio is initialized at different stages during boot depending on the board used. This commit moves the initialization of stdio to the beginning of periph_init(). This commit makes stdio available at a predictable and hardware independent stage during boot, which makes the use of stdio (e.g. via DEBUG(...)) during boot more robust.

Testing procedure

If examples/hello_world still compiles and prints, this would be a good indicator that stdio is still initialized properly.

Issues/PRs references

Addresses issue #10645
Related to PR #10615

Testing state

ARM familiy

ESP familiy

  • Some ESP8266 board
  • Some ESP32 board

RISC-V family

Native Family

  • "native" board (@maribu, maybe some else can confirm?)

ATmega Family

MSP430 Family

  • WSN430 Version 1.3b (Using FIT/IoT Lab @maribu)

MIPS Familiy


Update 1: Beatified markdown output
Update 2: Added tracker for testing
Update 3: Added testing results for Nulceo-F{103RB,303RE,401RE}
Update 4: Un-checked all tests because of force push
Update 5: Added new testing results for MSB-A2, Blue Pill, Arduino Due, Arduino Mega 2560
Update 6: Added test case for stdio_rtt to todo list
Update 7: Added test results for WiFire and Clicker board
Update 8: Removed stdio_rtt testing (which is not working in master anyway). Testing for ARM should be enough now.
Update 9: Added RISC-V test result
Update 10: Added MSP430 test result
Update 11: Added Arduino Uno test result

@maribu maribu added Impact: major The PR changes a significant part of the code base. It should be reviewed carefully Type: cleanup The issue proposes a clean-up / The PR cleans-up parts of the codebase / documentation Community: help wanted The contributors require help from other members of the community labels Jan 17, 2019
@maribu
Copy link
Copy Markdown
Member Author

maribu commented Jan 17, 2019

WARNING: This PR can easily break stuff... So the more testing the better. But testing is easy and fast :-)

@maribu maribu added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Jan 17, 2019
@maribu
Copy link
Copy Markdown
Member Author

maribu commented Jan 17, 2019

@kenrabold: Mind to test this on the hifive1?
@gschorcht: Mind to test this on the ESP family?
@kYc0o: Mind to test this for some ATmega boards? (The ATmega boards had consistent stdio initialization, so if it works for one it should theoretically work for all...)
@MrKevinWeiss / @dylad / @kaspar030: Mind to test for your favorite ARM board?

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Jan 17, 2019

Oh, the msp430 family also needs testing, of course...

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Jan 17, 2019

Being honest, the impact is actually minor, but the chance to break stuff is major. So I still felt to better set the "Impact: major" label in order to ring some alarm bells :-)

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Jan 17, 2019

I suggest to start with the testing once I managed to fix all compilation issues Murdock uncovers. I would also kindly ask to delay reviewing the code until that, so that I can squash/force-push right away. I'll leave a comment once I'm done and will stop un-authorized squashing/force pushing then.

@MrKevinWeiss
Copy link
Copy Markdown
Contributor

Can do!

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Jan 17, 2019

Done! Compilation errors fixed and squashed. Reviewing & testing can start now :-)

@MrKevinWeiss
Copy link
Copy Markdown
Contributor

I ran all tests (release specs compile_and_test_for_board.py) on the nucleo-f103rb/f401re/f303re. The only tests that failed are likely not due to this PR

nucleo-f103rb
Failures during test:
- [tests/driver_grove_ledbar](tests/driver_grove_ledbar/test.failed)
- [tests/driver_my9221](tests/driver_my9221/test.failed)
- [tests/gnrc_ipv6_ext](tests/gnrc_ipv6_ext/test.failed)
- [tests/gnrc_rpl_srh](tests/gnrc_rpl_srh/test.failed)
- [tests/pkg_fatfs_vfs](tests/pkg_fatfs_vfs/test.failed)
- [tests/xtimer_periodic_wakeup](tests/xtimer_periodic_wakeup/test.failed)
- [tests/xtimer_usleep_short](tests/xtimer_usleep_short/test.failed)
nucleo-f401re
Failures during test:
- [tests/driver_grove_ledbar](tests/driver_grove_ledbar/test.failed)
- [tests/driver_my9221](tests/driver_my9221/test.failed)
- [tests/gnrc_ipv6_ext](tests/gnrc_ipv6_ext/test.failed)
- [tests/gnrc_rpl_srh](tests/gnrc_rpl_srh/test.failed)
- [tests/pkg_fatfs_vfs](tests/pkg_fatfs_vfs/test.failed)
nucleo-f303re
Failures during test:
- [tests/driver_grove_ledbar](tests/driver_grove_ledbar/test.failed)
- [tests/driver_my9221](tests/driver_my9221/test.failed)
- [tests/gnrc_ipv6_ext](tests/gnrc_ipv6_ext/test.failed)
- [tests/gnrc_rpl_srh](tests/gnrc_rpl_srh/test.failed)
- [tests/pkg_fatfs_vfs](tests/pkg_fatfs_vfs/test.failed)

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Jan 18, 2019

@gschorcht @MrKevinWeiss: Thanks for the testing! :-)

@kenrabold
Copy link
Copy Markdown
Contributor

The PR as is breaks the HiFive1 board. The code for the board was never setup to call periph_init(), so the modifications stop all stdio output. Easy fix however. Similar to the msba2 mods, you can add the periph_init() call in board.c for HiFive1:

    /* Initialize newlib-nano library stubs */
    nanostubs_init();

    /* Initialize peripherals */
    periph_init();
}

(and the #include "periph/init.h" )

You will also need to update the Makefile.include in boards\hifive1 to include the periph modules:

# Export the peripheral drivers to be linked into the final binary:
export USEMODULE += periph

# include common periph code
export USEMODULE += periph_common

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Jan 19, 2019 via email

@cladmi cladmi added the Type: tracking The issue tracks and organizes the sub-tasks of a larger effort label Jan 21, 2019
@maribu
Copy link
Copy Markdown
Member Author

maribu commented Feb 5, 2019

With #10825 merged, this PR now works also on the HiFive1.

May I rebase against current master to fix the conflicts. (This would also make re-testing on the HiFive1 easier, as it than should work without needing to cherry-pick #10825)

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Feb 6, 2019

I will just interpret the absence of rejections as an implicit agreement that I may rebase to solve the conflicts :-)

Prior to this commit stdio is initialized at different stages during boot
depending on the board used. This commit moves the initialization of stdio
to the beginning of periph_init(). This commit makes stdio available at a
predictable and hardware independent stage during boot, which makes the use of
stdio (e.g. via DEBUG(...)) during boot more robust.
@maribu
Copy link
Copy Markdown
Member Author

maribu commented Feb 6, 2019

I did the rebase. Please test again. (Hopefully now the HiFive1 will also work :-))

@MrKevinWeiss
Copy link
Copy Markdown
Contributor

MrKevinWeiss commented Feb 6, 2019

- [tests/driver_grove_ledbar](tests/driver_grove_ledbar/test.failed)
- [tests/driver_hd44780](tests/driver_hd44780/test.failed)
- [tests/driver_my9221](tests/driver_my9221/test.failed)
- [tests/gnrc_ipv6_ext](tests/gnrc_ipv6_ext/test.failed)
- [tests/gnrc_rpl_srh](tests/gnrc_rpl_srh/test.failed)
- [tests/pkg_fatfs_vfs](tests/pkg_fatfs_vfs/test.failed)
- [tests/pthread_rwlock](tests/pthread_rwlock/test.failed)
- [tests/xtimer_periodic_wakeup](tests/xtimer_periodic_wakeup/test.failed)
- [tests/xtimer_usleep_short](tests/xtimer_usleep_short/test.failed)

The failing tests on the nucleo-f103rb, still don't think any problems caused by this PR.

@MrKevinWeiss
Copy link
Copy Markdown
Contributor

- [tests/driver_grove_ledbar](tests/driver_grove_ledbar/test.failed)
- [tests/driver_hd44780](tests/driver_hd44780/test.failed)
- [tests/driver_my9221](tests/driver_my9221/test.failed)
- [tests/gnrc_ipv6_ext](tests/gnrc_ipv6_ext/test.failed)
- [tests/gnrc_rpl_srh](tests/gnrc_rpl_srh/test.failed)
- [tests/pkg_fatfs_vfs](tests/pkg_fatfs_vfs/test.failed)

Seems OK for the nucleo-f303re

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Feb 16, 2019

@francois-berder: Thanks for pointing that out. Looks like a failed assertion. Maybe you can recompile with CFLAGS += DEBUG_ASSERT_VERBOSE added to your Makefile and test again to get more output?

@francois-berder
Copy link
Copy Markdown
Contributor

So I finally found why it was not working on the WiFire board (and Clicker as well). By default, STDIO_UART_DEV is set to UART_DEV(0). Since UART_DEV is not redefined, this means that STDIO_UART_DEV has value 0 and the call to uart_init(STDIO_UART_DEV, ...) triggers the assertion in cpu/mips_pic32_common/periph/uart.c:51 because, on PIC32MX and PIC32MZ, UART peripherals indexing start from 1 (as I think all other peripherals).

Here are the patches for pic32-wifire and pic32-clicker boards (I have tested this PR on both boards): mips-patches.zip

  * STDIO_UART_DEV was not set in board.h
  * Remove call to uart_init in board_init. UART is initialized
    in stdio_init.

Signed-off-by: Marian Buschsieweke <[email protected]>
  * STDIO_UART_DEV was not set in board.h
  * Remove call to uart_init in board_init. UART is initialized
    in stdio_init.

Signed-off-by: Marian Buschsieweke <[email protected]>
@maribu
Copy link
Copy Markdown
Member Author

maribu commented Feb 22, 2019

@francois-berder: Thanks for identifying and fixing the problem. I added your patches to this PR

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Feb 22, 2019

@kenrabold or @pyropeter: Could one of you test again with the HiFive1 and confirm that it is now working?

@OlegHahm: I heard you have some msp430 laying around? Would you mind to give this PR a test?

Would someone mind to confirm that it works still on AVR? (I tested successfully on the Arduino Uno and Arduino Mega2560, but I'm afraid of the "Works on My Machine Syndrome".)

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Feb 22, 2019

Btw: @haukepetersen noticed that stdio_rtt no longer works in master. So testing on the RuuviTag or the Thingy:52 is not possible anyway.

@kenrabold
Copy link
Copy Markdown
Contributor

@maribu For this PR to work on HiFive1, this change is needed in the HiFive1 board_init() function:

void board_init(void)
{
    /* Initialize CPU and clocks */
    board_init_clock();
    cpu_init();

The board_init_clock() call must happen before the cpu_init(). Because periph_init() is now called by cpu_init(), and that periph_init() will call down into stdio_init() which then calls uart_init(), the UART on the HiFive1 is configured based on the power on clock, and then that gets changed by board_init_clock(). That results in the divisor for the UART not being correct and the text going to serial output is all messed up.

So, just update the board.c for the HiFive1 to be like the code above and this PR will work.

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Feb 24, 2019

@kenrabold: Thanks for pointing that out. Can you test again to confirm it now works?

@kenrabold
Copy link
Copy Markdown
Contributor

@maribu - Yes, that works

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Mar 25, 2019

OK, I tested on the MSP430 platform myself using the wonderful FIT/IoT-Lab. It seems to work on every supported platform. May I squash?

Copy link
Copy Markdown
Contributor

@haukepetersen haukepetersen left a comment

Choose a reason for hiding this comment

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

Sorry for only jumping in so late, but I have to object on the concept this PR solves the problem at hand. See me inline comment for a IMHO better solution to this.

void periph_init(void)
{
/* initialize stdio first to allow DEBUG() during later stages */
#ifdef CPU_NATIVE
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.

This 'ifdef' hell should not be here. After all, periph_common is a platform independent module and adding all this platform dependencies here does destroy this whole concept!

The better way I see is to move these calls to the common platform modules, calling these functions from right before periph_init is called (or not in e.g. case of native). This way there is no need for any hard to read ifdefs anywhere, just a #ifdef MODULE_STDIO in for each supported architecture...

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.

One general remark: It is very rarely seen that someone jumps in at the very end of a process and after a lot of work has been done and asks for dropping core concepts of this process. For good reason. Also a lot of people tend to get very upset about such things.

That said, the idea of this PR is to assure that stdio is available when the periphs are initialized, so that using DEBUG() there is a save thing to do. This solves a real issue, as people starting to ENABLE_DEBUG in periph initialization would like to debug that periph code and don't want to think about why the debug output does not appear in the console or even crashes the board.

By having the stdio initialization here centralized, it is enforced that DEBUG() is safe to do in periph initialization. If it is up to the board/cpu, inconsistent behavior can easily sneak back it. So this idea about making RIOT less hardware dependent.

How about this instead:

#ifndef HAVE_ARCH_STDIO_INIT
static inline void arch_stdio_init(void)
{
    stdio_init();
}
#endif

void periph_init(void)
{
#if defined(MODULE_STDIO_UART) || defined(MODULE_STDIO_RTT)
    arch_stdio_init();
#endif
    ....
}

This would allow to provide a architecture specific void arch_stdio_init() if needed and handle the hardware specific stuff there.

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.

One general remark: It is very rarely seen that someone jumps in at the very end of a process

I am aware I and I honestly don't feel good about my behavior. I simply havn't seen this PR before (as I am not spending too much time on tracking PRs lately), and I stumbled upon this today - but when I see something like this I would simply feel bad about staying quit. So please accept my apologies.

On the topic at hand: I still disagree with putting this initialization into periph_common. periph_common is about peripheral, and STDIO is clearly not part of this. So just using periph_common/init.c as init trigger for some (critical, yes)) system module breaks with a global concept and is ergo always a bad idea!

So what would be needed is some generic 'pre-init' function reliably called by all platforms before periph_common/init, that can be used to bootstrap this kind of system modules. I don't see a large issue with doing such a thing, right?

Process wise I would be ok with merging this PR first (as it is already nicely tested), if someone promises to address this issue right afterwards.

Copy link
Copy Markdown
Member Author

@maribu maribu Mar 26, 2019

Choose a reason for hiding this comment

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

Maybe it is best to split off the mips32 changes, as any PR trying to reduce the hardware specific differences will depend on that. Maybe @francois-berder could open an PR for that, as he authored those commits anyway?

And maybe it would be a good idea to add get a general picture how the boot up process in an ideal RIOT board look like. Maybe a sequence diagram like this one in the doc of the Network Device Driver API could help. In that diagram the hardware specific part could be color coded, as those will likely still differ in various ways due to specific hardware dependent requirements.

Here is the sequence diagram I linked above (sadly with broken style):

seq_diagram

Such a diagram might help to at least have new added boards/architectures behave in a similar way and might be a good starting point to refactor and work out the differences of the existing boards.


Update: Fixed language/grammar

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Mar 26, 2019

@haukepetersen: This latest commit implements what I suggested as alternative to your feedback

@haukepetersen
Copy link
Copy Markdown
Contributor

I see, but it does not solve the issue for abusing the periph_comon/init.c for non-peripheral implemenations.

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Mar 26, 2019

I see, but it does not solve the issue for abusing the periph_comon/init.c for non-peripheral implemenations.

Maybe one could view stdio as an peripheral device in a broader sense, as it is an upper layer for interacting with peripheral devices such as keyboard/terminal/serial port on a PC and UART/RTT in the context of RIOT. Those devices are definitely peripheral devices and stdio is RIOT's default interface to UART when there is a human on the other side of the UART port.

Looking into the code of stdio_init() for the UART case, it basically initializes the UART device, thus, a peripheral device. So this argument is not as weak as it appears on the first view.

@kaspar030
Copy link
Copy Markdown
Contributor

Looking into the code of stdio_init() for the UART case, it basically initializes the UART device, thus, a peripheral device. So this argument is not as weak as it appears on the first view.

IMO that proves only that stdio_uart has periph/uart as a dependency. Thus initializing stdio_uart before the uart periph subsystem got a chance to be initialized (through periph_init()) is at least dangerous (or well, will lead to crashes).

I'd assume that stdio_rtt would have a similar issue. It depends on xtimer, which depends on periph_timer, which is also not initialized at this point.

@haukepetersen
Copy link
Copy Markdown
Contributor

I agree with @kaspar030, that just because stdio is using a periph driver, it is not a periph module on its own.

But one important point: the periph_common/init is there for initializing peripherals, that can be statically configured without taking any 'using module's' needs into account (e.g. i2c, spi, ...). 'timer' and 'uart' are a little different, as they are under full control if their users (e.g. xtimer, stdio, ...), and so these periph drivers are initialized by those modules directly, and are therefore independent from the periph_common module.

So again, we all agree, that stdio needs some extra care when it comes to initialization order. So I don't see a reason not to include its initialization in the platform specific code, especially considering the potential stdlib differences.

Side note (revisiting the discussion above): the segger_rtt mapping was not broken, it was my local python that prevented it from working. So now I can test those platforms in question again (at least when back in the office).

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Mar 26, 2019

@kaspar030, @haukepetersen:
OK, so the bottom line is that stdio initialization should be moved into cpu_init() just before the call to periph_init(), right?

How about in addition to that we add a test to periph_init() to verify that stdio is initialized and working during automated tests? That way it could be kind of enforced that DEBUG() is safe to call at least at that point in time during boot.

@haukepetersen
Copy link
Copy Markdown
Contributor

OK, so the bottom line is that stdio initialization should be moved into cpu_init() just before the call to periph_init(), right?

yes from my side.

How about in addition to that we add a test to periph_init() to verify that stdio is initialized and working during automated tests? That way it could be kind of enforced that DEBUG() is safe to call at least at that point in time during boot.

Personally, I think this is overkill. Of course we need to make sure, that DEBUG does not trigger any harmful behavior (as in hardfaults etc). So IMHO DEBUG, or more precise the STDIO, should just do nothing in case STDIO is not (yet) initialized or even selected for a build.

What we might want to think about in parallel, is to add some specific, general documentation on the boot order and the 'special cases' like stdio init and similar. Also a general rule about where it is save to use DEBUG and STDIO in the boot sequence could be added.

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Mar 26, 2019

Honestly, I'm not fully sure if a panic on DEBUG() before stdio is available is better than not having any indication that something went wrong. DEBUG() is not supposed to happen in production code. And a developer that added DEBUG() to some code and gets no output might have to look at a lot of things before tracing the issue down to not having stdio available.

But more generally: @MrKevinWeiss already implemented a safeguard in #10615 that drops UART output when used prior to initialization. But that change affects DEBUG() and production code alike and could potentially result in DEBUG() output getting lost. This PR is intended to be a kind-of follow up to make sure that this never happens.

Personally, I think this is overkill

Let me point out again that the test I suggested to add to periph_init() for automated testing is not supposed to be compiled in by default (as that is only useful for automated testing). It could be as simple as:

#ifdef TEST_STDIO_OUTPUT_ON_PERIPH_INIT
    puts("Initializing peripheral devices...");
#endif

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Mar 26, 2019

I remembered #10615 incorrectly. It will only drop output with DEVELHELP to fix boards not booting with ENABLE_DEBUG in some periph code.

@haukepetersen
Copy link
Copy Markdown
Contributor

I'd like to take a step back and focus on the core problem at hand: a clear definition of the point in time in the boot sequence, from which it is safe to use STDIO. We all agree, that this should as soon as possible, to allow for using STDIO early on during boot. If this is 'standardized', IMO there is no need for additional checking code if STDIO is available or not.

So what are the challenges:

  • (a) different stdio implementations have diverging dependencies on periph and other libraries (stdio_uart uses periph_uart, while stdio_rtt uses xtimer and with this periph_timer). So this is not something we can easily generalize.
  • (b) depending on the used platform, there are potential some prerequisites, that have to be done before stdio can be initialized
  • (c) ?

(b) is easy to solve by moving the init code into the platform specific init before periph_init().
For (a) it is not quite as trivial -> using stdio_uart one can use DEBUG in the timer driver, if using stdio_rtt one can clearly not. Calling it from periph_init does also not really solve this, right?!

@maribu
Copy link
Copy Markdown
Member Author

maribu commented Apr 10, 2019

I'm closing this PR now in favor of creating individual PRs per platform that move stdio_init() prior to periph_init(), ideally in cpu_init() just before periph_init().

@francois-berder: Would you mind to create such a PR for the mips platform? This PR would greatly depend on the code you contributed anyway.

@maribu maribu closed this Apr 10, 2019
@maribu maribu deleted the early_stdio branch May 11, 2020 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Community: help wanted The contributors require help from other members of the community Impact: major The PR changes a significant part of the code base. It should be reviewed carefully Type: cleanup The issue proposes a clean-up / The PR cleans-up parts of the codebase / documentation Type: tracking The issue tracks and organizes the sub-tasks of a larger effort

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants