Interrupts are executed in a critical section on RL78 port, how is this possible?

Hello,
Let’s start saying that I don’t have a real problem; my system is properly working. However, in the attempt to analyze how it was performing, I decided to measure how long the CPU stays in a critical section since it has few interrupts that must be handled with strict deadlines.
I’m doing this by toggling two GPIOs; one is set high when the interrupts are disabled (I modified the portENTER_CRITICAL and portEXIT_CRITICAL macros), and the other is driven high while the ISR is executing. Then, with a logic analyzer, I monitor the status of the GPIOs.

I expected the two GPIOs to never be high at the same time (at least as soon as the critical section are not entered in the ISR, which is not the case), but I actually see it! How is this possible? How can an ISR be executed inside a critical section? What am I missing? The interrupts are all with the same priority and the only one that can be executed with the interrupt disable is the one used for vPortYield.
If it makes any difference the interrupt under analysis does not cause any task to wake up, therefore it doesn’t use the portYIELD_FROM_ISR macro.

Thank you,
Davide.

the critical section disables interrupts only up to max_syscall priority, could it be that the isr you wish to monitor runs higher than max_sycall?

If I’m not wrong the MAX_SYSCALL is a feature of the ARM ports, I’m on the RL78 which has no reference to that.

no, MAX_SYSCALL is a standard feature of FreeRTOS designed to allow isrs that do not interact with the OS To run unaffected by the OS.

Ok, I was wrong, however in the RL78 port code I don’t see any reference to MAX_SYSCALL.

may be hidden in your freertosconfig.h, would you mind sharing? Also, what priority does the isr have that you are monitoring?

Sure, here the FreeRTOSConfig.h, I changed just few things with respect to the example. The CPU is the RL78G23 (https://www.renesas.com/en/document/man/rl78g23-users-manual-hardware), which is not officially supported, but the core is almost the same as the RL78G13 which is supported.

The priority of the interrupt is level 3 (low), as well as all the other interrupts the system uses

FreeRTOSConfig.h (4.8 KB)

Are you using the port from here:

FreeRTOS-Kernel/portable/GCC/RL78/portmacro.h at main · FreeRTOS/FreeRTOS-Kernel · GitHub
?

The port “hard disables” all interrupts at critical section enter time. It is hard to believe that that fails, nothing would work anymore if that was the case. Or are you using a custom port?

I’m using the IAR version (not the GCC one), but yes, I’m using the official port for the RL78 (I checked out the repo at V10.5.1).
I do totally agree that the critical section must be working, there must be something I’m missing. I’ll update here if I found out what is it!

Could you have put the GPIO toggle outside the critical section, not inside?

No, is not that…

I suspect that somewhere in the code there are direct calls to the portDISABLE_INTERRUPTS/portENABLE_INTERRUPTS or equivalents without proper increment/decrement of usCriticalNesting.

I wonder if this is an error with how the measurements are being made.

  • Is it possible that the resolution of your logic analyzer is not good enough to distinguish between the two GPIO events? The time between leaving the critical section and servicing an ISR is likely just a few clock cycles. If your logic analyzer/scope does not have a resolution that is about the same (or better) as your clock speed, you may have problems. If this is the problem, the only solution is to buy a better analyzer.

  • Does your logic analyzer/scope measure all inputs at the same instant, or does it scan over all inputs and measure them individually? If your scope/analyzer uses a mux to scan its inputs, it would be possible that you will see an incorrect state. (This is the logical equivalent of a rolling-shutter vs a global-shutter in photography). Cheap scopes and logic analyzer will do this, especially if they are measuring the signals using an ADC (rather than a dedicated digital input circuit). If this is the problem, swapping the channels on the analyzer/scope would change what you are seeing.

  • This seems really unlikely to me, but what about the microcontroller itself? Are the GPIOs you are using on the same bank? If not, do the different GPIO banks have different propagation delays between when the register gets set and the signal appears on the pin? If this is the problem (i really doubt this is the problem) you could move both GPIOs to the same bank.

How about electrical considerations?

  • Are your GPIOs in a push/pull configuration, or are they open-drain? An open-drain configuration could increase the fall time of your signal if there is too much impedance to ground. You could fix this by adding a strong pull-down, 1k should be plenty (make sure you don’t exceed the current capacity of your GPIO pins. 1k at 5v is 5mA, which should be well within their capabilities)

  • Does your probe or circuit have high capacitance? If the capacitance of your probe is too high, or there is too much capacitance in your signal path, the rise and fall times of your signals will be increased. You would have to fix this by getting a different probe and/or reducing the length of any wires.

  • Do your signals go through a logic level translator? This could change the rise/fall time of your signals. Probe on the other side of the level translator if this is the case.

  • Do your signals drive another component, like an LED? Can you remove that from the circuit and make the measurement again? I have had boards where the GPIO drives an LED in an open-drain configuration, but the test point was on the drain side. This caused high-frequency signals not to come through.

Finally - are you sure you modified the macro correctly? Are you doing a read-modify-write cycle on the GPIO register? If your architecture supports it, can you use an atomic read-modify-write instruction?

You can try to grep for that and see. Also, can you share your modifications to the port?

Thank you Alex for your hints, however:

The logic analizer runs at 500MS/s, more than 1 order of magnitude higher than the CPU clock, therefore that part of the measurement should be fine (I don’t know if it scans the inputs or sample them all at the same time, I think the second, anyway at 500MS/s it makes no difference).

For what regards the probe capacitance I don’t know, the logic analyzer is not a cheap toy (Saleae Logic Pro 16 Logic Analyzer), it is not even a NASA graded instrument but given it supports such high samplig rate I suppose the inputs are not with high capacitance.

The GPIOS are all on the same port and configured in push pull, no traslators, no leds, they are dedicated to debug therefore the lines comes from the MCU and go to a header connector.

I did that, I double check all the different macros in the productor’s libraries and I add the GPIO toggle on all of them. But nothing changes, I still see the ISR executing inside the CRITICAL SECTION.

I attach the portmacro.h with the changes (they are in the portDISABLE_INTERRUPTS and portENABLE_INTERRUPTS macros).

I did a similar thing to the other macros in the productor’s libraries.

portmacro.h (10.0 KB)

Is it possible that some other code also accesses the GPIO ports?

No, when I change the event that drives it the GPIO move coherently with the event, if something else is driving it it should impact also there.

EDIT: don’t look at the images, there is an error in the way I drive the DAC (TASKS trace)

However, no I do see something that might help.
This is a trace of the logic analyzer

And this is a zoom on the trigger (that is when the ISR is executed suring a critical section)

  • the first trace (named n3) is the GPIO that goes HIGH during the exectution of the ISR
  • the second trace (named n4) is the GPIO that goes HIGH inside a critical section
  • the third trace (named TASKS) is the DAC output used to inspect which task is executing (I used the technique proposed in the FreeRTOS website with the traceTASK_SWITCHED_IN macro, together with some other changes to set the DAC at the ISR execution)

First, there are some quite long critital sections here and there (the image shows 1.5ms or so, but in the full trace I see up to 10ms long critical section).
Second, when the ISR (apparently) executes during the critical section, the section is always quite long
Third, every time I see such a long critical section, the TASKS trace tells me that it just executed the _vPortYield rountine (which is associated to 0V).

This drives me to think that something is happening in vTaskSwitchContext that doesn’t properly drives the GPIO. The critical section macros must be working because the ISR executes and an actual problem in the critical section macros would break many parts of the system.

Ok, maybe I have an explaination, and it is strictly related to the architecture and to the port for RL78 (I’m not expert of the topic therefore I cannot say if it can be extended to other architectures).

What (I think) happens is that the actual status of the interrrupt is stored in the PSW register which is pushed and poped from the stack when ISR are executed:

Moreover, the portYIELD and equivalents functions of the RL78 port uses the BRK istruction and its ISR, which is never disabled:

Now, if portYIELD (BRK istruction) is called inside a critical section (and this happens few times inside the tasks.c file), the vPortYield function is executed immediately. If it happens that the yield makes to run a task with the PSW register with the IE flag set to 1, what you obtain is that the interrupts gets enabled before hitting the taskEXIT_CRITICAL marco.
If I’m not wrong, in the context of the vPortYield caller the interrupts will remain disabled till the end of the critical section, but for the task that is made to run with the yield, under the aforementioned conditions, the interruts are enabled.

Therefore, all this confusion comes from the yield inside a critical section, which is probably something the application code should not do, but inside the FreeRTOS kernel the logic is carefully designed to allow this.
In fact, a comment in xTaskGenericNotifyWait states:

/* All ports are written to allow a yield in a critical
* section (some will yield immediately, others wait until the
* critical section exits) - but it is not something that
* application code should ever do. */

Does all this make sense?

1 Like

yes, there are ports like that, which use an always enable SWI or just direct code for task switching which allow for critical sections to have “holes” in them (which don’t go through that Enable/Disable Interrupts function. If I remember right, this happens in some of the PIC ports too. For those ports, you would need to put something into that scheduler to set your GPIO also.

Yes, this seems correct and mostly likely is the reason of your observations. The following is the sequence of events:

  1. Task 1 enters critical section.
  2. Task 1 yields from inside the critical section which triggers yield handler synchronously.
  3. A context switch is performed and if the incoming task (task 2) was not inside the critical section when it was switched out (i.e. interrupts were not disabled when it was switched out), task 2 is scheduled with interrupts enabled.
  4. Later another context switch happens and Task 1 is scheduled with interrupts disabled.
  5. Interrupt nesting count is stored as part of task context, so it is also restored correctly when a task is scheduled:

The reason we do not see explicit EI/DI in the context save/restore code is because the hardware does this automatically -

Program status word contents are stored in the stack area upon vectored interrupt request is acknowledged or PUSH PSW instruction execution and are restored upon execution of the RETB, RETI and POP PSW instructions.

Thank you for diving deep and sharing!