rockbox/firmware/target/arm/system-arm-micro.h
Aidan MacDonald 5442622d88 arm: fix Cortex-M IRQ masking
The following inline assembly in set_irq_level() turned out
to have incorrect constraints:

    int newvalue = /* input parameter */;
    int oldvalue;

    asm volatile ("mrs %0, primask\n"
                  "msr primask, %1\n"
                  : "=r"(oldvalue) : "r"(newvalue));

leading to incorrect code generation for common cases like
disable_irq_save(), which compiles to:

    mov r5, #1
    mrs r5, primask
    msr primask, r5

...which doesn't disable IRQs at all, since both of the
operands got assigned to the same register; the write of
'oldvalue' clobbers the 'newvalue' input before it's used.

Apparently GCC assumes that input operands are read before
output operands are written. One way to fix this is adding
the '&' constraint: "=&r"(oldvalue), but it's better to
break things down into separate, simpler asm statements
which GCC can figure out itself.

Also add compiler memory barriers where primask is modified
to ensure loads/stores aren't incorrectly moved outside of
critical sections.

While here, optimize disable_irq_save() a bit by using the
cpsid instruction, which avoids the extra "mov" and register
allocation needed by "msr primask".

Change-Id: Iac94a76db5bac399a1cf028da4241a0473259a46
2026-01-23 16:47:46 -05:00

108 lines
2.9 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 by Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef SYSTEM_ARM_MICRO_H
#define SYSTEM_ARM_MICRO_H
#define IRQ_ENABLED 0x00
#define IRQ_DISABLED 0x01
#define IRQ_STATUS 0x01
#define HIGHEST_IRQ_LEVEL IRQ_DISABLED
/* For compatibility with ARM classic */
#define CPU_MODE_THREAD_CONTEXT 0
#define is_thread_context() \
(get_interrupt_number() == CPU_MODE_THREAD_CONTEXT)
/* Assert that the processor is in the desired execution mode
* mode: Processor mode value to test for
* rstatus...: Ignored; for compatibility with ARM classic only.
*/
#define ASSERT_CPU_MODE(mode, rstatus...) \
({ unsigned long __massert = (mode); \
unsigned long __mproc = get_interrupt_number(); \
if (__mproc != __massert) \
panicf("Incorrect CPU mode in %s (0x%02lx!=0x%02lx)", \
__func__, __mproc, __massert); })
/* Core-level interrupt masking */
static inline void enable_irq(void)
{
asm volatile ("cpsie i" ::: "memory");
}
static inline void disable_irq(void)
{
asm volatile ("cpsid i" ::: "memory");
}
static inline void restore_irq(int primask)
{
asm volatile ("msr primask, %0" :: "r"(primask) : "memory");
}
static inline int get_irq_level(void)
{
int primask;
asm volatile("mrs %0, primask" : "=r"(primask));
return primask;
}
static inline int disable_irq_save(void)
{
int oldlevel = get_irq_level();
disable_irq();
return oldlevel;
}
static inline int set_irq_level(int primask)
{
int oldvalue = get_irq_level();
restore_irq(primask);
return oldvalue;
}
static inline bool irq_enabled(void)
{
return get_irq_level() == IRQ_ENABLED;
}
static inline unsigned long get_interrupt_number(void)
{
unsigned long ipsr;
asm volatile ("mrs %0, ipsr" : "=r"(ipsr));
return ipsr & 0x1ff;
}
static inline void core_sleep(void)
{
asm volatile ("wfi");
enable_irq();
}
#endif /* SYSTEM_ARM_MICRO_H */