Direct task notification as semaphore - get count from ISR

Hi from an old newbie,
I am trying to replace an RTOS in an existing project with FreeRTOS 10.4.1.
An ISR needs to post a semaphore (or not) depending on existing semaphore count.
I use API call ulTaskNotifyValueClear(task_handler, 0U); to see the count. But when called from an ISR, assert fails in vPortEnterCritical() as interrupt-unsafe use of ulTaskGenericNotifyValueClear (which is not BTW documented as interrupt-unsafe).
How do I do this correctly? And if possible: what am I doing wrong?
Thanks! - Ark

FreeRTOS has a self-documenting naming convention for API calls allowed in ISRs.
Functions with the FromISR suffix can be called from interrupt context.
Seems that there is no FromISR variant of the function you need.
However, you could probably implement it yourself by e.g. using the original code from tasks.c but replacing taskENTER/EXIT_CRITICAL with taskENTER/EXIT_CRITICAL_FROM_ISR or add a feature request or a PR on GitHub.

Thank you, Hartmut,
I wrapped the call to ulTaskNotifyValueClear with taskENTER/EXIT_CRITICAL_FROM_ISR and it worked. Is it dumb luck or how it is supposed to be?

For full disclosure, I am not a friend (yet?) of FreeRTOS. Instead, I want to take the activity of RTOS replacement as an opportunity to treat an RTOS as a commodity, with RTOS-independent thin wrapper implementing a subset of functionality that I actually use.

So, I’d take an RTOS off the shelf, invest a couple of weeks to integrate, and then select the one based on stability, speed, features, certifiability, and LTS versions. Then move to commercial/supported versions.

I mean, I can’t count on future versions without further investigation. I will put a feature request though.

I think your approach with the ISR-safe critical section wrapper is good. To be honest it was my 1st tought but without knowing/verification I didn’t want to propose it…
BTW there is a CMSIS OS abstraction for a couple of underlying RTOS implementations.
Maybe it’s worth to have a look at it if you want to create OS agnostic applications.
On the other hand it might be a good idea to have your own tailored and lean abstraction.

As per hs2’s reply - there is an overarching rule in the API that functions ending in FromISR are interrupt safe. Other functions may be interrupt safe, but not necessarily, and it will also depend on the port you are using etc. The rationale is documented here:

To make the calling semantics simpler several macros are mapped to the same function. ulTaskNotifyValueClear() calls ulTaskGenericNotifyValueClear() - which already has an enter/exit critical section - so I’m not sure adding the same on the outside of the function will make much difference.

You may be able to do what you want using ulTaskNotifyAndQueryFromISR() with the action set to eNone - but that will send a notification albeit without changing the notification value.

Sorry, no, I can’t notify a task with nothing - in the existing setup of a project.

Do I understand it right that there are TWO pairs of exit/enter critical section: one for a task context, the other for ISR, and the ISR version allows me to use task-context API in an ISR within enter/exit sandwich? [If so, it would be a better setup for my back-integration task.]

Hi arkhas,

apologies in advance if this post should be regurgitating trivialties, but since many people don’t understand asymmetric mutual exclusion well, here is a short rundown of a few facts that may address some of your issues. In short, there rarely is a point in ISRs using critical sections - in the case of semaphore counts, for example, you would only need that if your ISR (while obtaining the semaphore count) gets interrupted by a higher priority ISR that signals the semaphore (thus changing the count) your lower priority ISR wishes to examine. I’ve yet to see (after 30 years of embedded development) a case of ISRs interacting like this. Normally synchronisation takes place between ISRs and tasks waiting for that particular device driver with no side effects to other components, as I’m sure you know.

Returning to your question, though: I believe you are basically right; most function pairs available in -FromISR and without it roughly go through the same code path, but using portSetInterruptMaskFromISR() vs. portEnterCritical(). For many MCUs (or ports, respectively), those translate to the same code, namely masking all ISRs up to configMAX_SYSCALL_INTERRUPT_PRIORITY or unmasking all.

A brief check reveales, however, that those API pairs don’t always end up in the same “worker code,” so it’s not only the bread that changes but also the meat. I’m sure there are very good reasons in every instance.

@RAc: Thank you for the link but… the only thing I picked up there is that FreeRTOS code is riddled with critical sections around fetching an intrinsically atomic value. It might be good for a port to define what is and what isn’t atomic and save time/code there.

@hs2: CMSIS is not, IMHO, exactly lean or stable. Most importantly, I don’t want to be locked in Cortex-M world.

@hs2: I was wrong. taskENTER_CRITICAL or taskENTER_CRITICAL_FROM_ISR didn’t change anything. The culprit is configASERT in vPortEnterCritical (line 318 of port.c of ARM_CM3 port). When commented out, everything is OK.

All, sorry for being as foolish as being unable to add a feature request in GitHub. Please someone do it for me. The request is an API to read, from an ISR, a task notification word, whether it is used as event flags, a semaphore, or my personal wallet :slight_smile:

An example use case is task overrun detection. Before I post a semaphore or an event, I may want to check if it is already posted, and sound an alarm if it is.
Another example is of a task that processes all characters from, say, a circular buffer until none is left. So, if more characters arrive, I only want to wake up the task if it is not already running.

Thanks All!

sure, I don’h have an issue with ISRs needing to obtain a semaphore count, that’s perfectly legit. It’s just that there normally is no need for an ISR to use a critical section when doing so as the only thread of execution that could cause a concurrency problem is a higher pri ISR giving the same semaphore (ISRs taking a semaphore, though possible when non blocking,is rather unusual in my experience).

Most of the stuff being wrapped by critical sections is stuff that won’t be atomic, because it will take several instructions to perform the action, and that whole action needs to be atom. There are not critical sections for reading a single value that will be atomic to read.

As to the issue with wrapping with tackENTER_CRITICAL_FROM_ISR, that operation will make much of the code safe, but it doesn’t know it so will still trigger the asserts. The one thing that it doesn’t get right is if the code that was executed was inside a scheduler suspend, then the code being used can’t reliably access anything in the scheduler.

Hmm. That would not be good. Can you link to examples of this so we can clean it up? You can link to lines directly in Github.

@rtel : It is not that easy to catch FreeRTOS red-handed. But FWIW my mentor taught me in the previous millennium to make solutions to simple problems simple, and to complex problems, possible. The already-infamous read semaphore count is mapped to ulTaskNotifyValueClear with nothing to clear, but it doesn’t know that there is nothing to clear, so it makes a critsec just in case. It looks like a pattern that functions are over-burdened. I am sure there are good reasons for this - but it does eat away from CPU cycles

@richard-damon : Do I read it correctly that when the scheduler is suspended, notifications/signaling that come from ISR might not be processed/routed correctly when the scheduler is reenabled? I hope I read it wrong!

BTW, taskENTER_CRITICAL_FROM_ISR could, couldn’t it, increment uxCriticalNesting if configASSERT is #define’d.

[IMHO, ideally, it would make sense to have “from ISR” flavors always available, even in a task context. Plain functions could be “lightweight” flavors for task context, within reason. I am not proposing creating tasks from ISR :slight_smile: ]

yes, you do read that wrong. I read Richard’s post to sketch what happens when task ENTER_CRITICAL_FROM_ISR is incorrectly used within a task context. Or the not ISR safe version inside an ISR? In any case, what he describes is NOT a regular scenario.

Why would that make sense? Not all MCUs may support that. As was mentioned earlier, in Cortex M MCUs, code can detect at run time whether it’s in an ISR or not, so on those PODs only one function/macro would suffice, but a) it would burn cycles in very heavily used code and b) supporting that explicitly would require different API sets for different MCUs for the same OS which is certainly not desireable. One of the things I like about FreeRTOS is that it does a marvelous job [IMHO] in supporting a wide range of targets with exactly the same kernel along with razor sharp border lines between the kernel and the ports. The different interrupt masking APIs for tasks vs. ISRs to me are sensible compromises towards that goal.

1 Like

The philosophy of having separate Task Level and ISR based functions is based on the idea that it simplifies both versions and the ISR context SHOULD generally be simple and obvious enough that the programmer can easily make the distinction. I think any case where it isn’t clearly obvious which to use has likely gotten to the point that the ISR is doing too much.

Yes, on some processors, the implementation of the FromISR versions are suitable for task level calls, but not on others. One of the goals of FreeRTOS was to make the base code to be widely portable, and not tied to a specific processor or family of processors, and that does impose some restrictions that might not be needed for a given processor.