ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Понедельник
20 июля
1596947 Топик полностью
Nikolay_Po (Вчера, 19:01, просмотров: 48) ответил Nikolay_Po на Доделал я порт FreeRTOS для QingKeV4 с включённой вложенностью прерываний. Как тов. Клен тут пояснял в своих примерах, ограничился только прерываниями с HPE, то есть, уровень вложенности до 3х включительно.
Тащ Клён! Как тебе такая переключалка стека? 
extern const uint32_t __freertos_irq_stack_top[];
extern const uint32_t __freertos_irq_stack_bottom[];
/* Prologue and epilogue for HPE-enabled interrupts */
#define ISR_prologue_epilogue_HPE(Vector, ISRcode) /* Vector is ISR vector name, ISR code is actual handler function declared with "WCH-Interrupt-fast" attribute */               \
	void __attribute__((naked)) Vector(void) { /* No registers out of HPE storage are used, so declare ISR vector as naked */                                                      \
		asm volatile("lui t0, %hi(__freertos_irq_stack_bottom);" /* Load stack bottom boundary address, higher 20 bits */                                                          \
					 "ori t0, t0, %lo(__freertos_irq_stack_bottom);" /* Load stack bottom boundary address, lower 12 bits */                                                     \
					 "bgeu sp, t0, 1f;" /* Compare current stack value against lower ISR stack boundary */                                                                         \
					 "add t0, zero, zero;" /* We are in task stack. Stack switch to ISR stack is needed. Set return value zero. */                                                 \
					 "csrrw sp, mscratch, sp;" /* Switch stack pointer to dedicated ISR stack to not pollute current task stack */                                                 \
					 "1:" /* Continue here if stack switch is not needed */                                                                                                        \
					 "addi sp, sp, -4;" /* Reserve a word in ISR stack */                                                                                                          \
					 "sw t0, 0(sp);"); /* Store stack switch requirement into ISR stack */                                                                                         \
		ISRcode(); /* Call actual ISR code. ISRcode should be declared with an "WCH-Interrupt-fast" attribute and may use a registers out of HPE storage this way */               \
		asm volatile("lw t0, 0(sp);" /* Restore stack switch flag from a stack */                                                                                                  \
					 "addi sp, sp, 4;" /* Free a word from ISR stack */                                                                                                            \
					 "bne t0, zero, 2f;" /* Compare flag with zero. Branch if not zero. */                                                                                         \
					 "csrrw sp, mscratch, sp;" /* Switch stack pointer from dedicated ISR stack back to current task stack */                                                      \
					 "2:" /* Go to here if no stack switch is needed */                                                                                                            \
					 "mret;"); /* Exit an interrupt */                                                                                                                             \
	}

В ассемблере выглядит так (если полезный обработчик объявить как static inline):


/* Начало прерывания, сюда указывает вектор. Тут условный переключатель стека. */
00000804: lui t0,0x2001c 00000808: ori t0,t0,0 0000080c: bgeu sp,t0,0x818 <SysTick_Handler+20> 00000810: add t0,zero,zero 00000814: csrrw sp,mscratch,sp 00000818: addi sp,sp,-4 0000081a: sw t0,0(sp) /* Тут полезный код обработчика прерывания. Ниже - эпилог. */
000008d0: lw t0,0(sp) 000008d2: addi sp,sp,4 000008d4: bnez t0,0x8dc <SysTick_Handler+216> 000008d8: csrrw sp,mscratch,sp 000008dc: mret

Фишка в чём: FreeRTOS, у неё стеки задач отдельны от общего стека всех прерываний. Поэтому, при входе в прерывание, нужно сначала переключить стек на стек прерываний, чтобы не занимать стек задачи, которую застало прерывание. А так как разрешены вложенные прерывания, переключение стека приходится делать условное. И ассемблерный пролог+эпилог это делают:
- При входе в прерывание, проверяется текущее значение указателя стека. Если оно вне стека прерываний (не в самом веру памяти), то выполняется переключение. Если прерывание вложенное и указатель стека находится уже в стеке прерываний - то переключение не делаем. Результат - ноль если переключали, не ноль - если не переключали - сохраняем в стек прерываний.
- При выходе, загружаем флаг переключения из стека. Если ноль - переключаем обратно. Если не ноль - переключения стека не нужно. Просто выходим из прерывания.

Была проблема с сохранением регистров. В сообщении выше, я решил её сохранением всех регистров, не сохраняемых HPE. И оно сработало. Но червячок перфекционизма не переставал меня глодать. И, когда мне напомнили, что можно вызвать не-naked функцию из naked-прерывания, картинка сложилась.

Итого:
- Пишем функцию обработчика прерывания. Объявляем её с атрибутом WCH-Interrupt-fast (или как там в сборке у тов. Клёна?). Так код обработчика не сохраняет регистры, сохраняемые HPE, но если портит регистры, не сохраняемые HPE, то эти регистры будут сохранены в стек прерываний автоматически. Функцию объявляем static inline __always_inline__, чтобы избежать лишнего вызова и возврата.
- Используем макрос, давая ему имя вектора прерывания и имя обработчика.

Перфекционизм удовлетворён. Ни одного лишнего сохранения, ни одного лишнего перехода. Была мысль вместо адреса стека, контролировать вложенность прерываний. И если вложенное - стек не переключать. Но нет. Прерывание низкого приоритета может не успеть переключить контекст, когда в него вложится другое прерывание. Поэтому - только контроль указателя стека по факту попадания в область стека прерываний.


P.S. По ходу внедрения переключалки, выявил такую ложку дёгтя - оно работает только для функций, которые нлайнятся. Толко заинлайненная функция, имея атрибут interrupt("WCH-Interrupt-fast"), будет сохранять контекст, но не будет делать mret в конце. Как только появляется вызов, то в конце функции, объявленной как прерывание, закономерно появляется mret, что не даёт восстановить стек - выход из прерывания выполняется раньше, чем дело доходит до части эпилога.