Tracing Hooks: Critical Sections

Hi! Just when you thought you were done with my long posts about tracing hooks…

I have been working on my tracer and also looking at the target implementations of both systemview and tracealyzer, and there is one more aspect about the FreeRTOS tracing hooks/API that I find unfortunate.

Most (all?) tracing hooks will end up writing to some shared resource - usually a RAM buffer used as a journal/ring buffer, a trace peripheral, a RAM buffer used for communication via systems like RTT etc. This requires the access to this resource to be done in a critical section to avoid overlapping/corrupted trace messages.

FreeRTOS supports (nested) critical sections in both ISR and task contexts, but this requires the tracing hook to know in which context it is running. Because there currently is no documentation (or mechanism) to enable this, all tracers that I have seen rely on writing their own per-platform ports to handle this, usually by detecting an interrupt context or writing an agnostic critical section.

In a sense FreeRTOS is an abstraction layer over platform-specific synchronization mechanisms, so it is unfortunate that a FreeRTOS API can not be properly consumed in a cross-platform fashion without platform-specific implementations.

As it stands right now, tracing hooks are called from any kind of context:

  • Before the scheduler is started
  • From tasks, from outside a critical section
  • From tasks, from critical sections.
  • From ISRs, from outside a critical section.
  • From ISRs, from critical sections.
  • From croutines (which I don’t claim to understand).

Some are only ever called from one place and one context, but others (like taskTASK_PRIORITY_DISINHERIT) can be called from a number of different contexts.

As far as I am aware, some ports (like CM4F one) feature a port macro that detects if an interurpt context is active, but this is not required - the unix port for one does not feature one.

If there were a mechanism to guarantee and document that every hook is either called from only a task or ISR context, or called from both but always in a critical section, this would enable tracers to be written purely against the FreeRTOS APIs.

I am, however, not sure if this is even feasible for API enter and exit tracing hooks - as far as I am aware it is allowed to call functions like UBaseType_t uxTaskGetTaskNumber( TaskHandle_t xTask ) from any context - so I don’t know if there would be any mechanism to track what context is currently active. But maybe supporting this for all normal “non-api” hooks is feasible?

I look forward to hearing your thoughts!

I agree that we should have different trace macros for task and ISR context. This would be consistent with our FromISR APIs. Looking at the code, it may be more work to do for some trace macros. A simpler solution can be to provide xPortIsInsideInterrupt API for your port. What do you think?

Glad to hear that there is interest for this :slight_smile:

Having separate FROM_ISR hooks would of course be optimal, but I don’t think it is strictly needed: Hooks can absolutely be called from both ISRs and tasks, as long as it is always done from a critical section, so that the tracing implementation can know that for that specific hook it does not need to enter/exit a critical semitone, and therefore does not need to know what critical section API it would need to call. But this of course would have to be documented and be part of the API contract.

I have had some look at the testing infrastructure, but I am not sure if there would be a simple way to setup tests that every tracing hook is called only from the context from which it is allowed to be called? Something akin to the asserts that certain APIs are only/never called from ISRs?

The reason I posted about this is because I am not 100% sure how all this could be achieved - Like I mentioned, I think it would be feasible to document the current contexts from which hooks are called and ensure that there are non that can be called from both ISRs and tasks from outside critical sections.

I don’t know if anything like this is even possible for ‘utility’ functions like uxTaskGetTaskNumber which are called from any context so the enter and exit hooks also will be. This would really require locking down all public API functions to be either ‘from task’ or ‘from isr’ but not both.

Absolutely - I was initially developing on the CM4F port and was using this, assuming all ports provided it. I then wanted to develop a POSIX test setup and realised that that port does not feature a 'xPortIsInsideInterrupt` so I cannot rely on that and would need special handling.

Is the POSIX port the exception here? In the end I am really mostly interested in finding some solution that is as port-agnostic as possible. Do most ports feature a xPortIsInsideInterrupt?

It seems as xPortIsInsideInterrupt is only implemented for Arm Cortex-M ports.

The question of critical sections in tracing is something we have been thinking about for quite a while at Percepio. There are a few events (hooks) like “Task Ready” that can be called from both Task and ISR context. And our TraceRecorder library allows for adding “user events” (custom logging points) basically anywhere in the code, also in high priority ISRs that may preeempt the kernel. For this reason we can’t rely on the standard FreeRTOS critical sections alone but need to make separate TraceRecorder ports for each processor family.

Having separated trace hooks for Task and ISR levels would not really help this much, at least not in the latter case. But having a standardized xPortIsInsideInterrupt would be very useful for both cases. That would make it far easier to support tracing across all FreeRTOS ports.

2 Likes

Not all ports have this implemented. I think we can keep implementing them incrementally.

Thank you for your inputs.