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

Add a simple re-entrancy lock for tu_printf. #2653

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

HiFiPhile
Copy link
Collaborator

Describe the PR
Add a simple re-entrancy lock for TU_LOG, should resolve #2652 in most cases. An interlocked version is needed for multicore MCU.

int tu_printf_r(const char *format, ...)
{
// Simple re-entrancy lock, not safe for multi-core MCU
static volatile bool lock = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Even on a single core, volatile is not sufficient to prevent inconsistencies due to ISR execution interrupting another instance executing from user space. This needs an actual critical section, or similar.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Could you make a real example with disassembly ?

Choose a reason for hiding this comment

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

The problem is with this sequence:

  if (lock) {
    return -1;
  }
  // <--- ISR COMES HERE
  lock = true;

To make that work on uniprocessor with interrupt, then interrupt must be disabled between the test and the setting to true:

something like:

  disable_interrupts();
  if (lock) {
    return -1;
  }
  lock = true;
  enable_interrupts()  

Choose a reason for hiding this comment

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

And this is of course wrong, more like:

  bool is_locked;
  disable_interrupts();
  is_locked = lock;
  if (!is_locked)
      lock = true;
  enable_interrupts()  
  if (is_locked)
      return (-1);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hi,

The problem is with this sequence:

  if (lock) {
    return -1;
  }
  // <--- ISR COMES HERE
  lock = true;

When ISR happened here, vprintf hasn't been executed, so ISR will execute vprintf in it's context then return to normal context. There is no case where the execution of vprintf is interrupted.

Choose a reason for hiding this comment

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

Actually you're right, sorry. need more coffee.

Choose a reason for hiding this comment

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

But: what protect your IRQ from interrupting another vprintf() call, outside tinyusb?

Choose a reason for hiding this comment

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

BTW: I'm using a stm32h747 with tinyusb, and I would love to see your DWC2 DMA patch going in, which is why I was lurking....

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But: what protect your IRQ from interrupting another vprintf() call, outside tinyusb?

Good question, currently TU_LOG_* are available for user space logging.

BTW: I'm using a stm32h747 with tinyusb, and I would love to see your DWC2 DMA patch going in

We are few maintaining the repo, if you don't want to wait you can try the testing branch.

@hathach
Copy link
Owner

hathach commented Jun 15, 2024

Thanks @HiFiPhile, though I think printf lock should be done in user space by implementing (nano)libc lock in _write_r(). Since not all retarget needs lock e.g. segger rtt

@HiFiPhile
Copy link
Collaborator Author

though I think printf lock should be done in user space by implementing (nano)libc lock in _write_r(). Since not all retarget needs lock e.g. segger rtt

The problem is libc's printf() itself is not reentrant as it works on global data, not just the I/O part.

https://github.com/bminor/newlib/blob/1ed15161362b2eb5649b049b7ab0aaf8097bd43a/newlib/libc/stdio/printf.c#L52

@hathach
Copy link
Owner

hathach commented Jun 17, 2024

though I think printf lock should be done in user space by implementing (nano)libc lock in _write_r(). Since not all retarget needs lock e.g. segger rtt

The problem is libc's printf() itself is not reentrant as it works on global data, not just the I/O part.

https://github.com/bminor/newlib/blob/1ed15161362b2eb5649b049b7ab0aaf8097bd43a/newlib/libc/stdio/printf.c#L52

I may miss a thing though: the newlib reentrant implementation seems to the the exact same thing as this. For logging within ISR, we only do log in isr with level 3, which I used mainly with segger RTT. I will revise the code to see if there is any log1/log2 in the isr.

PS: oh, I see you mean the global _REENT is also used by other libc like malloc() and not only printf(). For this, how about moving this guard into the _write(), I still think it should be done at user level.

@HiFiPhile
Copy link
Collaborator Author

Eh... I think reentrant is kind of implementation dependent, I just read IAR's manual:

Most parts of the DLIB runtime environment are reentrant, but the following functions
and parts are not reentrant because they need static data:
Heap functions—malloc, free, realloc, calloc, etc. and the C++ operators new and delete
...
Functions that use files or the heap in some way. This includes scanf, sscanf, getchar, getwchar,
putchar, and putwchar. In addition, if you are using the options --printf_multibytes and --dlib_config=Full,
the printf and sprintf functions (or any variants) can also use the heap.
...
Remedies for this are:
● Do not use non-reentrant functions in interrupt service routines
● Guard calls to a non-reentrant function by a mutex, or a secure region, etc

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

Successfully merging this pull request may close these issues.

Interrupt lock missing in debug log functions
4 participants