Миро Самек. Построение простых систем на ARM-контроллерах с использованием инструментов GNU (перевод)


Часть 8

В этой части описываются низкоуровневые функции-оболочки "ARM_irq()" и "ARM_fiq()", анонсированные в шестой части статьи. Их задача - обеспечить обработку вложенных прерываний, каковая обработка невозможно при использовании атрибута GNU gcc "__attribute__ ((interrupt ("IRQ")))".

Возможно, самой интересной стороной описываемой в данной статье реализации является её хорошая совместимость с архитектурой нового ядра ARM v7-M (так называемого Cortex-M3) [14]. А именно, вся низкоуровневая "кухня" ядра ARM заключена внутри ассемблерных функций-оболочек, что позволяет обработчикам прерываний, подключаемым к контроллеру прерываний, быть обычными функциями на языке "Си".

"Си"-код обработчиков прерываний вызывается в том же режиме работы ядра (SYSTEM), что и код, вызываемый из "main()" (уровень приложения). Кроме того, ассемблерная оболочка однозначно предотвращает использование IRQ/FIQ стеков, размещая вместо этого прерывания всех типов (IRQ и FIQ) в SYSTEM/USER стеке.

Контекст, сохраняемый в стеке, оптимизировн для использования с высокоуровневыми языками и, как предлагается в спецификации ARM v7-M, оболочка сохраняет только 8 регистров, затираемых при соблюдении стандарта вызовов процедур в архитектуре ARM (ARM Architecture Procedure Calling Standard - AAPCS) [14].

На самом деле, программная оболочка прерывания делает в точности тот же стековый кадр ("SPSR", "PC", "LR", "R12", "R3", "R2", "R1", "R0"), что и аппаратура ядра ARM v7-M [14].

(Тут, возможно, следует заметить, что описываемая здесь методика обработки прерываний отличается от проверенного пути, предлагаемого обычно [8, 9, 10, 13]. Большинство работ рекомендует инициализировать таблицу векторов так, чтобы использовать автовекторизацию приоритетного контроллера (см. шестую часть статьи). )

Обработчики прерываний, адресами которых инициализируется контроллер прерываний, требуют весьма специфическую последовательность при входе и выходе, что не позволяет им быть обычными "Си"-функциями. Кроме того, обработчики работают в режиме IRQ или FIQ и используют IRQ и FIQ стеки (как минимум, для части сохраняемого контекста). Соответственно, стековые кадры и само использование стека в традиционных решениях заметно отличается от реализации ARM v7-M.


8.1 Оболочка обработчика IRQ "ARM_irq()"

Функция-оболочка "ARM_irq()", предназначенная для обработки IRQ прерываний, расположена в файле "arm_exc.s" из состава кода, сопровождающего статью, и приведена здесь целиком.

 
Листинг 8.1 Ассемблерная "обёртка" "ARM_irq()" (arm_exc.s)

      .equ  NO_IRQ,     0x80              /* mask to disable IRQ */
      .equ  NO_FIQ,     0x40              /* mask to disable FIQ */
      .equ  NO_INT,     (NO_IRQ | NO_FIQ) /*mask to disable IRQ and FIQ */
      .equ  FIQ_MODE,   0x11
      .equ  IRQ_MODE,   0x12
      .equ  SYS_MODE,   0x1F

      .text
(1)   .code 32

(2)   .section .text.fastcode
      ...

      ARM_irq:
      /* IRQ entry {{{ */
(3)         MOV     r13,r0                /* save r0 in r13_IRQ */
(4)         SUB     r0,lr,#4              /* put return address in r0_SYS */
(5)         MOV     lr,r1                 /* save r1 in r14_IRQ (lr) */
(6)         MRS     r1,spsr               /* put the SPSR in r1_SYS */

(7)         MSR     cpsr_c,#(SYS_MODE | NO_IRQ)
                                          /* SYSTEM, no IRQ, but FIQ enabled! */
(8)         STMFD   sp!,{r0,r1}           /* save SPSR and PC on SYS stack */
(9)         STMFD   sp!,{r2-r3,r12,lr}    /* save APCS-regs on SYS stack */
(10)        MOV     r0,sp                 /* make the sp_SYS visible to IRQ mode */
(11)        SUB     sp,sp,#(2*4)          /* make room for stacking (r0, r1) */

(12)        MSR     cpsr_c,#(IRQ_MODE | NO_IRQ)
                                          /* IRQ mode, IRQ/FIQ disabled */
(13)        STMFD   r0!,{r13,r14}         /* finish saving the context (r0,r1)*/

(14)        MSR     cpsr_c,#(SYS_MODE | NO_IRQ) /* SYSTEM mode, IRQ disabled */
      /* IRQ entry }}} */

      /* NOTE: BSP_irq might re-enable IRQ interrupts (the FIQ is enabled
      * already), if IRQs are prioritized by an interrupt controller.
      */
(15)        LDR     r12,=BSP_irq
(16)        MOV     lr,pc                 /* copy the return address to link */
(17)        BX      r12                   /* call the C IRQ-handler */

      /* IRQ exit {{{ */
(18)        MSR     cpsr_c,#(SYS_MODE | NO_INT)
                                          /* SYSTEM mode, IRQ/FIQ disabled */
(19)        MOV     r0,sp                 /* make sp_SYS visible to IRQ mode */
(20)        ADD     sp,sp,#(8*4)          /* fake unstacking 8 regs from sp_SYS */

(21)        MSR     cpsr_c,#(IRQ_MODE | NO_INT)
                                          /* IRQ mode, both IRQ/FIQ disabled */
(22)        MOV     sp,r0                 /* copy sp_SYS to sp_IRQ */
(23)        LDR     r0,[sp,#(7*4)]        /* load the saved SPSR from the stack */
(24)        MSR     spsr_cxsf,r0          /* copy it into spsr_IRQ */

(25)        LDMFD   sp,{r0-r3,r12,lr}^    /* unstack all saved SYS registers */
(26)        NOP                           /* can't access banked reg immediately */
(27)        LDR     lr,[sp,#(6*4)]        /* load return address from the SYS stack */
(28)        MOVS    pc,lr                 /* return restoring CPSR from SPSR */
      /* IRQ exit }}} */
										

Отметим ключевые моменты реализации:

(1) Низкоуровневые IRQ/FIQ обработчики должны быть написаны с использованием 32-разрядных инструкций ARM, так как ядро автоматически переключается в режим ARM при вхождении в прерывание.

(2) "ARM_irq()" функция помещена в специальную секцию (".text.fastcode"), которая будет загружена в оперативную память (см. третью часть статьи) для ускорения выполнения.

(3) IRQ стек не используется и регистр из переключаемого банка "r13_IRQ" ("sp_IRQ") используется как временный регистр для хранения регистра "r0" из контекста SYSTEM.

Премечание. При вхождении в состояние прерывания IRQ ядро ARM выставляет бит "I" в регистре "CPSR" (CPSR[7] = 1), но оставляет бит "F" (обычно сброшенный) без изменений. Это означает, что возможные прерывания IRQ запрещаются, а прерывания FIQ - нет. Подразумевается, что прерывание FIQ может возникнуть в тот момент, когда ядро ARM находится в режиме IRQ. Обработчик прерываний FIQ "ARM_fiq()", который будет рассмотрен позднее, может безопасно перехватывать контроль у "ARM_irq()" везде, где прерывания FIQ не запрещены.

(4) Теперь регистр "r0" может быть перезаписан адресом возврата из прерывания, который должен сохраняться в стеке режима SYSTEM.

(5) С этого момента регистр из переключаемого банка "lr_IRQ" может использоваться для временного хранения регистра "r1" из контекста SYSTEM.

(6) Теперь "r1" может быть перезаписан значением регистра "spsr_IRQ" (Saved Program Status Register), который должен быть сохранён в стеке SYSTEM.

(7) Режим ядра переключается в SYSTEM с запрещёнными IRQ и разрешёнными FIQ перрываниями. Переключение производится для получения доступа к регистрам контекста SYSTEM.

Примечание. На этом этапе бит "F" регистра "CPSR" сбрасывается в нуль (что означает, что FIQ разрешены). В числе прочего это позволяет обойти вторую проблему, описанную в технической заметке фирмы ARM "Что произойдёт, если прерывание возникнет в момент его запрещения ?" (см. седьмую часть статьи).

(8) Регистр "SPSR" и адрес возврата сохраняются в стеке SYSTEM.

(9) Все регистры (кроме "r0" и "r1") используемые по стандарту AAPCS (ARM Architecture Procedure Call Standard) [14] сохраняются в стеке SYSTEM.

(10) Указатель стека SYSTEM помещается в регистр "r0", чтобы он был доступен в режиме IRQ.

(11) Указатель стека SYSTEM корректируется, чтобы дать место для двух дополнительных регистров сохраняемого контекста IRQ. Скорректировав указатель, обработчик IRQ прерывания может сохранять FIQ прерывания в разрешённом состоянии, не рискуя повредить область стека SYSTEM, зарезервированную под контекст IRQ.

(12) Ядро переключается обратно в режим IRQ с запрещёнными IRQ и разрешёнными FIQ прерываниями. Это делается для получения доступа к содержимому остальных переключаемых регистров режима IRQ.

(13) Остатки контекста в составе регистров "r0" и "r1" сохранены в стеке SYSTEM (их копии по-прежнему находятся в регистрах переключаемого банка режима IRQ "r14_IRQ" и "r13_IRQ" соответственно). С настоящего момента сохранённый стековый кадр режима SYSTEM содержит 8 регистров и выглядит как показано ниже (в точности как стековый кадр прерывания в ARM v7-M [14]):

 
										Старшие адреса
                       SPSR
                       PC     (адрес возврата)
      |  направление   LR
      |  роста         R12
      V  стека         R3
                       R2
                       R1
                       R0     <-- SP_SYS
      Младшие адреса

(14) Ядро опять переключается в режим SYSTEM с запрещёнными IRQ и разрешёнными FIQ прерываниями. Отметим, что указатель стека "sp_SYS" смотрит на вершину стекового кадра, так как был скорректирован после первого переключения в режим SYSTEM в точке (11).

(15-17) Аппаратно зависимая функция "BSP_irq()" вызывается для обработки прерывания на уровне приложения. Обратите внимание, что "BSP_irq()" - обычная "Си"-функция, которая может быть скомпилирована под набор инструкций ARM или THUMB. Обычно она использует аппаратный контроллер прерываний (такой, как AIC фирмы Atmel) для выбора вектора текущего прерывания (см. шестую часть статьи).

Примечание. Функция "BSP_irq()" получает управление с запрещёнными IRQ (и разрешёнными FIQ) прерываниями, но она может разблокировать IRQ, если ядро снабжено контроллером прерываний, приоритизирующим IRQ прерывания аппаратно.

(18) IRQ и FIQ прерывания запрещаются на время модификации указателя стека.

(19) Текущее значение указателя стека "sp_SYS" копируется в регистр "r0", чтобы сделать его видимым в режиме IRQ.

(20) Перед выходом из режима SYSTEM проводится корректировка значения регистра "sp_SYS" и из стека целиком выбрасывается стековый кадр прерывания, состоящий из 8 регистров. Это действие возвращает стек SYSTEM в состояние, предшествующее прерыванию [а в регистре "r0" содержится некорректированное значение].

Премечание. Несмотря на то, что указатель стека исправлен, его содержимое всё ещё не восстановлено. Полная блокировка прерываний в данной точке критически важна для сохранения содержимого памяти ниже скорректированного указателя стека [стек растёт в сторону младших адресов памяти].

(21) Ядро переводится в режим IRQ с запрещёнными IRQ и FIQ прерываниями для завершающего возврата из прерывания.

(22) Сохранённый некорректированный указатель стека SYSTEM копируется в "sp_IRQ" регистрового банка режима IRQ, устанавливая его тем самым на "старую" вершину стека SYSTEM.

(23-24) Значение регистра "SPSR" выбирается из "пустой" памяти на 7 слов ниже вершины стека и записывается в регистр "SPSR_irq".

(25) 6 регистров восстанавливаются из стека SYSTEM. Отметим специальную форму инструкции "LDM" (с символом "^" на конце), которая означает, что регистры восстанавливаются из стека SYSTEM/USER. Отметим также, что специальная инструкция "LDM(2)" не позволяет проводить обратную запись, и указатель стека не меняется. (За дополнительной информацией обратитель к секции "LDM(2)" в руководстве "ARM Architecture Reference Manual" [13]).

(26) Очень важно не обращаться к регистровому банку сразу после специальной инструкции "LDM(2)".

(27) Из стека выбирается адрес возврата. Отметим, что он расположен на 6 слов ниже "старой" вершины стека.

(28) Для возврата из прерывания предполагается загрузка указателя команд (PC) адресом возврата, а регистра "CPSR" - содержимым "SPSR", каковые действия осуществляются специальным ваниантом инструкции "MOVS pc, lr".


8.2 Оболочка обработчика FIQ "ARM_fiq()"

Функция-оболочка "ARM_fiq()" предназначена для обработки FIQ прерываний, расположена в файле "arm_exc.s" в составе кода, сопровождающего статью, и приведена здесь целиком.

 
Листинг 8.2 Ассемблерная "обёртка" "ARM_fiq()" (arm_exc.s)

(1)   ARM_fiq:
      /* FIQ entry {{{ */
            MOV     r13,r0                /* save r0 in r13_FIQ */
            SUB     r0,lr,#4              /* put return address in r0_SYS */
            MOV     lr,r1                 /* save r1 in r14_FIQ (lr) */
            MRS     r1,spsr               /* put the SPSR in r1_SYS */

(2)         MSR     cpsr_c,#(SYS_MODE | NO_INT)
                                          /* SYSTEM mode, IRQ/FIQ disabled */
            STMFD   sp!,{r0,r1}           /* save SPSR and PC on SYS stack */
            STMFD   sp!,{r2-r3,r12,lr}    /* save APCS-regs on SYS stack */
            MOV     r0,sp                 /* make the sp_SYS visible to FIQ mode */
            SUB     sp,sp,#(2*4)          /* make room for stacking (r0, SPSR) */

            MSR     cpsr_c,#(FIQ_MODE | NO_INT) /* FIQ mode, IRQ/FIQ disabled */
            STMFD   r0!,{r13,r14}         /* finish saving the context (r0,r1)*/

            MSR     cpsr_c,#(SYS_MODE | NO_INT)
                                          /* SYSTEM mode, IRQ/FIQ disabled */
      /* FIQ entry }}} */

      /* NOTE: NOTE: BSP_fiq must NEVER enable IRQ/FIQ interrrupts!
      */
            LDR     r12,=BSP_fiq
            MOV     lr,pc                 /* store the return address */
(3)         BX      r12                   /* call the C FIQ-handler (ARM/THUMB)

      /* FIQ exit {{{ */                  /* both IRQ/FIQ disabled (see NOTE above) */
            MOV     r0,sp                 /* make sp_SYS visible to FIQ mode */
            ADD     sp,sp,#(8*4)          /* fake unstacking 8 registers from sp_SYS */

            MSR     cpsr_c,#(FIQ_MODE | NO_INT) /* FIQ mode, IRQ/FIQ disabled */
            MOV     sp,r0                 /* copy sp_SYS to sp_FIQ */
            LDR     r0,[sp,#(7*4)]        /* load the saved SPSR from the stack */
            MSR     spsr_cxsf,r0          /* copy it into spsr_FIQ */

            LDMFD   sp,{r0-r3,r12,lr}^    /* unstack all saved SYSTEM registers */
            NOP                           /* can't access banked reg immediately */
            LDR     lr,[sp,#(6*4)]        /* load return address from the SYS stack */
            MOVS    pc,lr                 /* return restoring CPSR from SPSR */
      /* FIQ exit }}} */
										

Функция "ARM_fiq()" очень похожа на обработчик IRQ, обсуждавшийся выше, за исключением режима FIQ, используемого вместо режима IRQ. Комментарии поясняют только небольшие, но существенные отличия в способе запрещения прерываний и взаимодействия с обработчиком, написанным на языке "Си" - "BSP_fiq()".

(1) Вхождение в обработчик FIQ производится с запрещёнными IRQ и FIQ прерываниями и, таким образом, режим FIQ не виден из любого другого режима работы ядра.

(2) Режим ядра переключается на SYSTEM для получения доступа к указателю на стек SYSTEM. Обращаем внимание на то, что запрет на IRQ и FIQ прерывания сохраняется всё время работы "ARM_fiq()".

(3) "Си"-функция "BSP_fiq()" вызывается для обработки прерывания с уровня приложения. Отметим, что "BSP_fiq()" - обычная функция на языке "Си" и может быть скомпилирована под набор инструкций ARM или THUMB. В отличие от IRQ, FIQ прерывания не проходят через приоритетный контроллер и функция "BSP_fiq()" обязана сохранять блокировку прерываний.

Примечание. "BSP_fiq()" получает управление в момент когда оба типа прерываний зпарещены и ни при каких обстоятельствах не должна их разрешать. Обычно линия FIQ в ядре ARM не приоритизируется, несмотря на то, что проходит через контроллер прерываний.

ПредпросмотрAttachmentSize
bare_metal_arm_systems_html.zip327.73 КБ
blinky_files.zip173.94 КБ