Electronic – Are there platforms where disabling/restoring interrupts from ISR should be done differently than from non-ISR context

cinterruptsrtos

I'm familiar with several real-time kernels: AVIX, FreeRTOS, TNKernel, and in all of them we have 2 versions of nearly all functions: one for calling from task, and second one for calling from ISR.

Of course it makes sense for functions that could switch context and/or sleep: obviously, ISR can't sleep, and context switch should be done in different manner. But there are several functions that do not switch context nor sleep: say, it may return system tick count, or set up software timer, etc.

Now, I'm implementing my own kernel: TNeoKernel, which has well-formed code and is carefully tested, and I'm considering to invent "universal" functions sometimes: the ones that can be called from either task or ISR context. But since all three aforementioned kernels use separate functions, I'm afraid I'm going to do something wrong.

Say, in task and ISR context, TNKernel uses different routines for disabling/restoring interrupts, but as far as I see, the only possible difference is that ISR functions may be "compiled out" as an optimization if the target platform doesn't support nested interrupts. But if target platform supports nested interrupts, then disabling/restoring interrupts looks absolutely the same for task and ISR context.

So, my question is: are there platforms on which disabling/restoring interrupts from ISR should be done differently than from non-ISR context?

If there are no such platforms, I'd prefer to go with "universal" functions. If you have any comments on this approach, they are highly appreciated.

UPD: I don't like to have two set of functions because they lead to notable code duplication and complication. Say, I need to provide a function that should start software timer. Here is what it looks like:

enum TN_RCode _tn_timer_start(struct TN_Timer *timer, TN_Timeout timeout)
{
   /* ... real job is done here ... */
}

/*
 * Function to be called from task
 */
enum TN_RCode tn_timer_start(struct TN_Timer *timer, TN_Timeout timeout)
{
   TN_INTSAVE_DATA;  //-- define the variable to store interrupt status,
                     //   it is used by TN_INT_DIS_SAVE()
                     //   and TN_INT_RESTORE()
   enum TN_RCode rc = TN_RC_OK;

   //-- check that function is called from right context
   if (!tn_is_task_context()){
      rc = TN_RC_WCONTEXT;
      goto out;
   }

   //-- disable interrupts
   TN_INT_DIS_SAVE();

   //-- perform real job, after all
   rc = _tn_timer_start(timer, timeout);

   //-- restore interrupts state
   TN_INT_RESTORE();

out:
   return rc;
}

/*
 * Function to be called from ISR
 */
enum TN_RCode tn_timer_istart(struct TN_Timer *timer, TN_Timeout timeout)
{
   TN_INTSAVE_DATA_INT;    //-- define the variable to store interrupt status, 
                           //   it is used by TN_INT_DIS_SAVE()                
                           //   and TN_INT_RESTORE()                           
   enum TN_RCode rc = TN_RC_OK;

   //-- check that function is called from right context
   if (!tn_is_isr_context()){
      rc = TN_RC_WCONTEXT;
      goto out;
   }

   //-- disable interrupts
   TN_INT_IDIS_SAVE();

   //-- perform real job, after all
   rc = _tn_timer_start(timer, timeout);

   //-- restore interrupts state
   TN_INT_IRESTORE();

out:
   return rc;
}

So, we need wrappers like the ones above for nearly all system function. This is a kind of inconvenience, for me as a kernel developer as well as for kernel users.

The only difference is that different macros are used: for task, these are TN_INTSAVE_DATA, TN_INT_DIS_SAVE(), TN_INT_RESTORE(); for interrupts these are TN_INTSAVE_DATA_INT, TN_INT_IDIS_SAVE(), TN_INT_IRESTORE().

For the platforms that support nested interrupts (ARM, PIC32), these macros are identical. For other platforms that don't support nested interrupts, TN_INTSAVE_DATA_INT, TN_INT_IDIS_SAVE() and TN_INT_IRESTORE() are expanded to nothing. So it is a bit of performance optimization, but the cost is too high in my opinion: it's harder to maintain, it's not so convenient to use, and the code size increases.

Best Answer

I believe there are several more driving forces for having two sets of functions, than you have mentioned.

  1. Those RTOSs are designed to be portable to many different processor Instruction Set Architectures (ISAs), and they also try to ensure developers can write systems which are portable. So they have to accommodate all of the variability of processor ISAs in ways which hide the differences between ISAs from the RTOS users.
  2. Some of the ISAs used under an RTOS have both 'user' mode, and 'privileged' mode. 'User' mode will not have access to all of the system, and specifically might not be able to block interrupts. Further some of the functions might not be able to run in 'user' mode, and may 'trap' to a privileged mode exception handler to actually do the work. So having two 'name spaces' aligned to the two CPU privilege states might be helpful to the RTOS and application developers.
  3. IMHO it is much easier to remember to use a function from one 'name space' (e.g. 'user' mode) than it is to figure out exactly which function can be used in any specific context. Specifically, it is much easier for all the functions to come in a 'user application' name space, and a 'privilaged-system-component' name space. So, even if the code is identical, it is simpler for the application developer to use the functions in the 'application' or 'user' name space. If that is an advantage for the application developer, then it is more helpful for for the RTOS provider to 'duplicate' the code, into a function in the other 'name space' than not have the duplicated function.

Summary: IMHO having two sets of functions, effectively constituting two different 'name spaces' ('user application' and 'systemcomponent'), makes it easier for the 'application' developer, and developers adding extra 'privileged-system-component' to use.

It might also be easier to have two different function sets to make it easier to manage interrupts raised during the code running in two different system states.

It may be relatively straightforward to create the two alternatives using some carefully thought out pre-processor macros, to minimise code duplication.

If you are not intending anyone else to use your OS, or your OS will treat exceptions raised during 'user' mode and 'system' mode identically, then you can probably ignore the idea.

However, if you don't know how it might all work (for example this is the first time you have written an RTOS), or you think it might change, then you might want to think carefully about how you would re-introduce the two 'name spaces' part way through developing your OS.

Related Topic