Electronics > Microcontrollers
Globally enabling/disabling CH32V003 interrupts with the mstatus CSR
HwAoRrDk:
I'm attempting to put together some universal use-case code for disabling and enabling interrupts globally on the CH32V003, for the purposes of having an 'atomic' section of code that cannot be interrupted. Something like avr-libc's ATOMIC_BLOCK construct. The primary aim is to make it universal so that it can be used within ISRs without messing things up.
Now, it seems a common way to globally disable interrupts on the CH32V003 is to toggle the MIE flag of the mstatus register. Indeed, there are functions __disable_irq() and __enable_irq() in the WCH HAL/SPL that do just this. However, they are kind of a brute-force way of doing it because they simply blindly clear and set MIE (and MPIE for some reason) regardless of its prior value.
To begin with, I want to confirm that my understanding of the flags in the mstatus register is correct. Firstly, because the CH32V003 operates exclusively in Machine mode, I only need be concerned with the M* fields, correct? Second, is regarding the relationship between MIE and MPIE. Am I correct in understanding that MPIE serves to save the value of MIE when an interrupt occurs, and that MIE gets restored to the value of MPIE by the MRET instruction when returning from an ISR? However one thing confuses me. The CPU manual says (section 7.2, p29): "it should be noted that in the QingKe V2 series microprocessors, MIE is not updated to 0 at this time before entering the last level of interrupt to ensure that the interrupt nesting in Machine mode continues to be executed". What does this mean? Are they trying to say that normally (assuming nesting is enabled), MIE is not set to zero during an ISR, but in one circumstance it is? What is this "last level" of interrupt? Are they saying that when an interrupt of the lowest priority occurs, MIE does get set to zero? Is that the absolute lowest (i.e. 15) or whatever the configured lowest is?
Anyway, regardless of that, what I think I need to do to implement a kind of atomic block macro is to save and restore the MIE, as follows:
1. Read the value of mstatus and save the MIE value.
2. Clear the MIE bit.
3. Execute the block's code.
4. Set MIE back to whatever the saved value is.
This way, I don't believe it will matter what context this atomic block construct is used in - outside of or inside of an ISR. I don't believe I need to do anything with MPIE, nor with MPP (apparently it only can be and accept a value of 0b11).
What I have come up with to implement this is:
--- Code: ---#define interrupt_atomic_block() \
for( \
uint32_t __interrupt_atomic_block_mie_saved = __interrupt_save_clear_mie(), __interrupt_atomic_block_loop = 1; \
__interrupt_atomic_block_loop; \
__interrupt_restore_mie(__interrupt_atomic_block_mie_saved), __interrupt_atomic_block_loop = 0 \
)
#define MSTATUS_MIE_MASK 0x8
__attribute__((always_inline)) static inline uint32_t __interrupt_save_clear_mie(void) {
uint32_t value;
__asm volatile(
"csrrc %0, mstatus, %1"
: "=r" (value) // Outputs
: "i" (MSTATUS_MIE_MASK) // Inputs
: // No clobbers
);
return value & MSTATUS_MIE_MASK;
}
__attribute__((always_inline)) static inline void __interrupt_restore_mie(uint32_t value) {
value &= MSTATUS_MIE_MASK;
__asm volatile(
"csrs mstatus, %0"
: // No outputs
: "r" (value) // Inputs
: // No clobbers
);
}
--- End code ---
Is all my understanding correct? Are there any flaws in my plan or implementation? Any advice gratefully received.
HwAoRrDk:
By the way, anybody know why objdump's disassembler does not seem to recognise RISC-V CSR instructions?
For example, for me it only outputs the aforementioned CSRRC instruction as ".4byte 0x300477f3".
Do I need to tell it what RISC-V ISA and/or extensions I'm using? Looking at the --help output, it says there is a -Mpriv-spec=SPEC option, but I have no idea which value to pass (it says supported values are "1.9.1 1.10 1.11 1.12") for the WCH QingKeV2, which apparently conforms to the older ISA spec version 2.2.
Edit: If I pass in -Mpriv-spec=1.12, I get a warning of "mis-matched privilege spec set by priv-spec=1.12, the elf privilege attribute is 1.11". So if it knows that from the ELF file, why doesn't it decode the CSR instructions? Explicitly passing -Mpriv-spec=1.11 doesn't change anything though. :-//
SiliconWizard:
Do you happen to know what version of binutils you are using?
HwAoRrDk:
--- Quote from: SiliconWizard on March 22, 2023, 08:49:49 pm ---Do you happen to know what version of binutils you are using?
--- End quote ---
The output of --version is "GNU objdump (xPack GNU RISC-V Embedded GCC x86_64) 2.38".
SiliconWizard:
--- Quote from: HwAoRrDk on March 22, 2023, 08:05:03 pm ---1. Read the value of mstatus and save the MIE value.
2. Clear the MIE bit.
3. Execute the block's code.
4. Set MIE back to whatever the saved value is.
--- End quote ---
That looks correct. It's a relatively common way of doing this, even if it's a bit drastic (sometimes you may want to disable interrupts on a lower level, depends on the application.)
But certainly that will prevent the block of code to be interrupted. It would not prevent an exception, though.
--- Quote from: HwAoRrDk on March 22, 2023, 08:05:03 pm ---What I have come up with to implement this is:
--- Code: ---#define interrupt_atomic_block() \
for( \
uint32_t __interrupt_atomic_block_mie_saved = __interrupt_save_clear_mie(), __interrupt_atomic_block_loop = 1; \
__interrupt_atomic_block_loop; \
__interrupt_restore_mie(__interrupt_atomic_block_mie_saved), __interrupt_atomic_block_loop = 0 \
)
#define MSTATUS_MIE_MASK 0x8
__attribute__((always_inline)) static inline uint32_t __interrupt_save_clear_mie(void) {
uint32_t value;
__asm volatile(
"csrrc %0, mstatus, %1"
: "=r" (value) // Outputs
: "i" (MSTATUS_MIE_MASK) // Inputs
: // No clobbers
);
return value & MSTATUS_MIE_MASK;
}
__attribute__((always_inline)) static inline void __interrupt_restore_mie(uint32_t value) {
value &= MSTATUS_MIE_MASK;
__asm volatile(
"csrs mstatus, %0"
: // No outputs
: "r" (value) // Inputs
: // No clobbers
);
}
--- End code ---
Is all my understanding correct? Are there any flaws in my plan or implementation? Any advice gratefully received.
--- End quote ---
The problem I see at first sight is with your __interrupt_restore_mie() function.
(I suppose the 'csrs' is a typo and it's 'csrrs'?)
If it's strictly used in the context you describe (ie. the MIE bit would be cleared when calling __interrupt_restore_mie()), then it should work fine.
But strictly speaking, if __interrupt_restore_mie() is called, the MIE bit is currently set and the original value (passed to the function) is 0, it won't clear the MIE bit.
Probably just a detail - you may not have intended this function to allow that. But taken independently, this function would have a misleading behavior: if 'value' is 0, it has no effect.
Navigation
[0] Message Index
[#] Next page
Go to full version