Go Down

Topic: Determining Whether Interrupts Are Enabled (Read 3108 times) previous topic - next topic

RayLivingston

I have some libraries which do non-atomic operations on shared data structures (like fifos, etc.), which may be called from either interrupt or non-interrupt contexts.  So far, I've been able to get away with "cheating" by having the "push" operations performed only from an interrupt context, and the "pop" operations only from a non-interrupt context.  So, the "push" code assumes interrupts are disabled, and the "pop" code explicitly turns interrupts off at the start, and turns interrupts back on at the end.  But, I now want to be able to call both routines from any context.

So the question is:  How do I save and restore the interrupt state?  It's not clear to me how interrupts are masked/disabled when an ISR is called, and the Due has a multitude of ways of doing it.  I'd basically like to create replacements for interrupts() and noInterrupts().  The noInterrupts() replacement would save the current interrupt enable state, then disable all interrupts.  The interrupts() replacement would restore the interrupt enable state to exactly what it was before noInterrupts was called.

Any ideas how to do this on a Due?

Regards,
Ray L.

MorganS

Do you know where to look to find the source code for noInterrupts()? I would start there.
"The problem is in the code you didn't post."

RayLivingston

Do you know where to look to find the source code for noInterrupts()? I would start there.
With great difficulty, I did find where those methods are defined, and it was no help, as they are inline assembler, and I don't know enough about either ARM assembler or the SAM register set to understand what they are doing.  Nor does it give a clue how to save and restore the state, rather than setting or clearing it.  I'm hoping someone here has already cracked this nut, because I can see it would likely take me hours to learn enough to be able sort it out.

Here are the definitions, from core_cmFunc.h:

Code: [Select]

/** \brief  Enable IRQ Interrupts

  This function enables IRQ interrupts by clearing the I-bit in the CPSR.
  Can only be executed in Privileged modes.
 */
__attribute__( ( always_inline ) ) static __INLINE void __enable_irq(void)
{
  __ASM volatile ("cpsie i");
}


/** \brief  Disable IRQ Interrupts

  This function disables IRQ interrupts by setting the I-bit in the CPSR.
  Can only be executed in Privileged modes.
 */
__attribute__( ( always_inline ) ) static __INLINE void __disable_irq(void)
{
  __ASM volatile ("cpsid i");
}


Regards,
Ray L.

RayLivingston

I THINK what I need to do is read and write the PRIMASK register, which I THINK can be done with:

Code: [Select]

mrs r0, PRIMASK  // to read PRIMASK to r0
msr PRIMASK, r0  // to write r0 to PRIMASK


but, again, I don't know enough ARM assembly, or the gnu register conventions, to know how to use these to write functions that read PRIMASK, and store the value, then read the stored value, and write it back to PRIMASK.

Anyone here know enough to know how do that?

Regards,
Ray L.

Magician

Code: [Select]
/** \brief  Enable External Interrupt

    This function enables a device specific interrupt in the NVIC interrupt controller.
    The interrupt number cannot be a negative value.

    \param [in]      IRQn  Number of the external interrupt to enable
 */
static __INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{
  NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* enable interrupt */
}


Looks like name of the register is ISER, and more from data sheet:
Quote
12.20.2
Interrupt Set-enable Registers
The ISER0-ISER1 register enables interrupts, and show which interrupts are enabled. See:

RayLivingston

Code: [Select]
/** \brief  Enable External Interrupt

    This function enables a device specific interrupt in the NVIC interrupt controller.
    The interrupt number cannot be a negative value.

    \param [in]      IRQn  Number of the external interrupt to enable
 */
static __INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{
  NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* enable interrupt */
}


Looks like name of the register is ISER, and more from data sheet:
That is for disabling specific interrupt sources.  I need to be able to disable and enable ALL interrupt sources.

Regards,
Ray L.

RayLivingston

#6
Oct 22, 2015, 06:32 am Last Edit: Oct 22, 2015, 06:47 am by RayLivingston
Here is the one function I think I need, in the hopes that someone here can help with the actual two or three lines of inline assembler code to implement it:

Code: [Select]

uint32_t getPRIMASK(void);


All this need to do is read the PRIMASK register, and return it.  I can then use interrupts and noInterrupts() to do what I need as follows:

Code: [Select]

boolean primask = getPRIMASK();
noInterrupts();

// do whatever here...

if (primask)
    interrupts();


If I am understanding correctly how gcc allocates registers, using r0 for the return value of a function, this might be as simple as:

Code: [Select]

uint32_t getPRIMASK(void)
{
   register uint32_t pmask asm("r4");
    asm("mrs r4, PRIMASK" : "=4" (pmask));
    return pmask;
}


Regards,
Ray L.

Magician

I tried this:
Code: [Select]
            reg32 = __get_PRIMASK ();
            Serial.print("\n\tPRIMASK: ");   //debug
            Serial.print( reg32, BIN);

//            __set_PRIMASK (1);

            reg32 = __get_PRIMASK ();
            Serial.print("\n\tPRIMASK: ");   //debug
            Serial.print( reg32, BIN);
             
            __set_PRIMASK (0);

            reg32 = __get_PRIMASK ();
            Serial.print("\n\tPRIMASK: ");   //debug
            Serial.print( reg32, BIN);


It reports 0. If I uncomment set(1) than my Due hanging up. It means gcc supports read-write.
Register is one-bit wide

RayLivingston

I tried this:
Code: [Select]
            reg32 = __get_PRIMASK ();
            Serial.print("\n\tPRIMASK: ");   //debug
            Serial.print( reg32, BIN);

//            __set_PRIMASK (1);

            reg32 = __get_PRIMASK ();
            Serial.print("\n\tPRIMASK: ");   //debug
            Serial.print( reg32, BIN);
             
            __set_PRIMASK (0);

            reg32 = __get_PRIMASK ();
            Serial.print("\n\tPRIMASK: ");   //debug
            Serial.print( reg32, BIN);


It reports 0. If I uncomment set(1) than my Due hanging up. It means gcc supports read-write.
Register is one-bit wide
I will give that a try.  Thanks!

Regards,
Ray L.

RayLivingston

Yes! That works perfectly, as proven by:

Code: [Select]

noInterrupts();
boolean fPRIMASK = __get_PRIMASK();
interrupts();
pConsole->printf("noInterrupts() -> fPRIMASK=%d\n", fPRIMASK);
fPRIMASK = __get_PRIMASK();
pConsole->printf("interrupts() -> fPRIMASK=%d\n", fPRIMASK);


which prints:

Quote
noInterrupts() -> fPRIMASK=1
interrupts() -> fPRIMASK=0
Thanks again!

Regards,
Ray L.

Magician


RayLivingston

My interrupt handlers are now working perfectly. 

But, since I'm not getting any responses on the Programming Forum, perhaps someone here knows how to change compile options?  I need to know how to remove the -no-rtti option, which is preventing me from using dynamic_cast.  I'm sure there's a configuration file somewhere that sets the compiler options, but I have no clue where to look...

Regards,
Ray L.

RayLivingston


TheRevva

I don't know all the 'specifics' of your initial interrupts issue, but it's a RARE day that I would ever want to disable ALL interrupts.
You might find that start 'missing' an occasional time-critical interrupt that's not even related in any way to your code leading to overrun errors.

For me, I find that 'protecting' those critical areas of code with atomic spinlocks, semaphores, mutexs (lookup LDREX and STREX) will do the job just nicely.  However, a LOT depends on how your code is structured so YMMV.  Maybe I'm just being 'old school'?
My 'rule of thumb' is to do the barest minimum while I am in interrupt context leaving everything possible to a 'bottom half' handler.

Go Up