8000 cpu/avr8_common: Optimize context switch by nandojve · Pull Request #19799 · RIOT-OS/RIOT · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

cpu/avr8_common: Optimize context switch #19799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions boards/atxmega-a1u-xpro/include/board.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ extern "C" {
#define BTN0_PIN GPIO_PIN(PORT_Q, 2)
#define BTN0_MODE (GPIO_IN | GPIO_OPC_PU | GPIO_SLEW_RATE)
#define BTN0_INT_FLANK (GPIO_ISC_FALLING | GPIO_LVL_LOW)

#define BTN1_PIN GPIO_PIN(PORT_C, 2)
#define BTN1_MODE (GPIO_IN | GPIO_OPC_PU | GPIO_SLEW_RATE)
#define BTN1_INT_FLANK (GPIO_ISC_FALLING | GPIO_LVL_LOW)
/** @} */

/**
Expand Down
1 change: 1 addition & 0 deletions cpu/avr8_common/avr8_cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
*/
uint8_t mcusr_mirror __attribute__((section(".noinit")));
uint8_t soft_rst __attribute__((section(".noinit")));
uint8_t avr8_isr_stack[ISR_STACKSIZE] __attribute__((section(".data")));
#if (AVR8_STATE_IRQ_USE_SRAM)
uint8_t avr8_state_irq_count_sram = 0;
#endif
Expand Down
164 changes: 143 additions & 21 deletions cpu/avr8_common/include/cpu.h
10000
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ extern "C"
{
#endif

extern uint8_t avr8_isr_stack[];

/**
* @name BOD monitoring when CPU is on sleep
* @{
Expand All @@ -67,18 +69,6 @@ extern "C"
#define PERIPH_I2C_NEED_WRITE_REGS
/** @} */

/**
* @brief Run this code on entering interrupt routines
*/
static inline void avr8_enter_isr(void)
{
/* This flag is only called from IRQ context, and nested IRQs are not
* supported as of now. The flag will be unset before the IRQ context is
* left, so no need to use memory barriers or atomics here
*/
++avr8_state_irq_count;
}

/**
* @brief Compute UART TX channel
*
Expand Down Expand Up @@ -117,10 +107,142 @@ static inline int avr8_is_uart_tx_pending(void)
return avr8_state_uart;
}

/*
* The AVR-8 uses a shared stack only for process interrupts and switch context.
*/
#ifndef ISR_STACKSIZE
#define ISR_STACKSIZE (512)
#endif

/**
* @brief Enter ISR routine
*
* It saves the register context for process an ISR.
*/
__attribute__((always_inline))
static inline void avr8_isr_prolog(void)
{
/* This flag is only called from IRQ context. The value will be handled
* before ISR context is left by @ref avr8_isr_epilog. ATxmega requires a
* cli() as first instruction inside an ISR to create a critical section
* around `avr8_state_irq_count`.
*/
__asm__ volatile (
/* Register pair r24/25 are used to update `avr8_state_irq_count` content.
* This registers are used to test conditions related to context switch in
* ISR at @ref avr8_isr_epilog.
*/
"push r30 \n\t"
"in r30, __SREG__ \n\t"
"push r30 \n\t"
"push r31 \n\t"
#if defined(CPU_ATXMEGA)
"cli \n\t"
#endif
#if (AVR8_STATE_IRQ_USE_SRAM)
"lds r31, %[state] \n\t"
"subi r31, 0xFF \n\t"
"sts %[state], r31 \n\t"
#else
"in r31, %[state] \n\t"
"subi r31, 0xFF \n\t"
"out %[state], r31 \n\t"
#endif
#if defined(CPU_ATXMEGA)
"sei \n\t"
#endif
#if __AVR_HAVE_RAMPZ__
"in r30, __RAMPZ__ \n\t"
"push r30 \n\t"
#endif
#if __AVR_HAVE_RAMPX__
"in r30, __RAMPX__ \n\t"
"push r30 \n\t"
#endif
#if __AVR_HAVE_RAMPD__
"in r30, __RAMPD__ \n\t"
"push r30 \n\t"
#endif
"push r0 \n\t"
"push r1 \n\t"
"clr r1 \n\t"
"push r18 \n\t"
"push r19 \n\t"
"push r20 \n\t"
"push r21 \n\t"
"push r22 \n\t"
"push r23 \n\t"
"push r24 \n\t"
"push r25 \n\t"
"push r26 \n\t"
"push r27 \n\t"
/*
* Load irq_stack
*/
#if defined(CPU_ATXMEGA)
"cli \n\t"
#endif
"cpi r31, 1 \n\t"
"brne skip_save_if_nested_isr_%= \n\t"
"ldi r30, lo8(%[irq_stack]) \n\t"
"ldi r31, hi8(%[irq_stack]) \n\t"
"in r24, __SP_L__ \n\t"
"st z, r24 \n\t"
"in r24, __SP_H__ \n\t"
"st -z, r24 \n\t"
"subi r30, 1 \n\t"
"sbci r31, 0 \n\t"
"out __SP_L__, r30 \n\t"
"out __SP_H__, r31 \n\t"
"skip_save_if_nested_isr_%=: \n\t"
#if defined(CPU_ATXMEGA)
"sei \n\t"
#endif
: /* no output */
#if (AVR8_STATE_IRQ_USE_SRAM)
: [state] "" (avr8_state_irq_count),
#else
: [state] "I" (_SFR_IO_ADDR(avr8_state_irq_count)),
#endif
[irq_stack] "" (avr8_isr_stack + ISR_STACKSIZE - 1)
: "memory"
);
}

/**
* @brief Restore register context from ISR
*/
__attribute__((naked))
void avr8_isr_epilog(void);

/**
* @brief Run this code on entering interrupt routines
*
* Notes:
* - This code must not be shared.
* - This code must not be a call or jmp.
*/
__attribute__((always_inline))
static inline void avr8_enter_isr(void)
{
avr8_isr_prolog();
}

/**
* @brief Run this code on exiting interrupt routines
*/
void avr8_exit_isr(void);
__attribute__((always_inline))
static inline void avr8_exit_isr(void)
{
/* Let's not add more address in stack and save some code sharing
* avr8_isr_epilog.
*/
#if (FLASHEND <= 0x1FFF)
__asm__ volatile ("rjmp avr8_isr_epilog" : : : "memory");
#else
__asm__ volatile ("jmp avr8_isr_epilog" : : : "memory");
#endif
}

/**
* @brief Initialization of the CPU clock
Expand All @@ -137,18 +259,18 @@ static inline uinttxtptr_t __attribute__((always_inline)) cpu_get_caller_pc(void
{
uinttxtptr_t addr;
__asm__ volatile(
"ldi %D[dest], 0" "\n\t"
"ldi %D[dest], 0 \n\t"
#if __AVR_3_BYTE_PC__
"pop %C[dest] " "\n\t"
"pop %C[dest] \n\t"
#else
"ldi %C[dest], 0" "\n\t"
"ldi %C[dest], 0 \n\t"
#endif
"pop %B[dest]" "\n\t"
"pop %A[dest]" "\n\t"
"push %A[dest]" "\n\t"
"push %B[dest]" "\n\t"
"pop %B[dest] \n\t"
"pop %A[dest] \n\t"
"push %A[dest] \n\t"
"push %B[dest] \n\t"
#if __AVR_3_BYTE_PC__
"push %C[dest] " "\n\t"
"push %C[dest] \n\t"
#endif
: [dest] "=r"(addr)
: /* no inputs */
Expand Down
2 changes: 1 addition & 1 deletion cpu/avr8_common/include/irq_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ __attribute__((always_inline)) static inline bool irq_is_enabled(void)
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define AVR8_ISR(vector, function, ...) \
ISR(vector, ISR_BLOCK) \
ISR(vector, ISR_NAKED) \
{ \
avr8_enter_isr(); \
function(__VA_ARGS__); \
Expand Down
77 changes: 68 additions & 9 deletions cpu/avr8_common/include/states_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,77 @@ extern uint8_t avr8_state_uart_sram; /**< UART state variable. */
/** @} */

/**
* @name Global variable containing the current state of the MCU
* @{
*
* @note This variable is updated from IRQ context; access to it should
* be wrapped into @ref irq_disable and @ref irq_restore should be
* used.
* @brief MCU ISR State
*
* Contents:
*
* | Label | Description |
* |:-------|:--------------------------------------------------------------|
* | IRQ | This variable is incremented when in IRQ context |
* The `avr8_state_irq_count` is used to indicate that system is processing an
* ISR for AVR-8 devices. It stores how deep system is processing a nested
* interrupt. ATxmega have three selectable interrupt levels for any interrupt:
* low, medium and high and are controlled by PMIC. ATmega requires that users
* re-enable interrupts after executing the `_isr_handle` method to enable
* nested IRQs in low priority interrupts. ATxmega PMIC is not enough to
* determine the current state of MCU ISR in RIOT-OS because scheduler may
* perform a context switch inside any IRQ.
*
* If the system is running outside an interrupt, `avr8_state_irq_count` will
* have a 0 value. When one or more interrupt vectors are activated,
* `avr8_state_irq_count` will be incremented and have a value greater than 0.
* These operations are performed by the pair @ref avr8_enter_isr and
* @ref avr8_exit_isr.
*
* An 3-level nested IRQ illustration can be visualized below:
*
* int-3
* ↯
* +----------+
* | high lvl |
* int-2 +----------+
* ↯ | |
* +---------+ +---------+
* | mid lvl | | mid lvl |
* int-1 +---------+ +---------+
* ↯ | |
* +---------+ +---------+
* | low lvl | | low lvl |
* +---------+ +---------+ ↯ can switch
* | | context here
* +----------+ +----------+
* | thread A | | thread B |
* +----------+ +----------+
*
* At @ref avr8_exit_isr scheduler may require a switch context. That can be
* performed only when `avr8_state_irq_count` is equal to zero. This is
* necessary to avoid stack corruption and since system is processing interrupts
* since priority inheritance happened.
*
* The AVR-8 allow nested IRQ and MCUs cores perform with different behaviour, a
* custom IRQ management was developed. The code is simple and must use the
* following skeleton:
*
* The `_isr_handle` method represents the body of an ISR routine. This is the
* code that handles the IRQ itself. It can be a shared function and don't
* need any special care.
*
* The IRQ method `_isr_handle` should have minimal parameters. It is
* recommended up to 2 and convention is to use the first parameter as the
* instance and the second parameter usually represent the unit inside that
* instance.
*
* Example: UART uses 1 parameter and Timers, usually 2.
*
* static inline void _uart_isr_handler(instance)
* {
* ...
* }
* AVR8_ISR(UART_0_RXC_ISR, _uart_isr_handler, 0);
*
* static inline void _tmr_isr_handler(instance, channel)
* {
* ...
* }
* AVR8_ISR(TIMER_0_ISRA, _tmr_isr_handler, 0, 0);
* AVR8_ISR(TIMER_0_ISRB, _tmr_isr_handler, 0, 1);
*/
#if (AVR8_STATE_IRQ_USE_SRAM)
extern uint8_t avr8_state_irq_count_sram; /**< IRQ state variable. */
Expand Down
Loading
0