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


Часть 9

В этой части разбором инициализации таблицы векторов и контроллера прерываний, а также программ-обработчиков прерываний (ISR), написанных на языке "Си", завершается описание обработки прерываний для ARM.

Представленные примеры относятся к контроллеру прерываний фирмы Atmel (Advanced Interrupt Controller - AIC) [15], но могут быть легко модифицированны для других моделей (например, VIC фирмы NXP). Кроме того, кратко описывается методика обработки других исключений ARM, таких как "Undefined Instruction" или "Data Abort".


9.1 Функция обработчика "BSP_irq()"

Как говорилось в части 6 низкоуровневая функция-оболочка "ARM_irq()" вызывает функцию "BSP_irq()", написанную на языке "Си", скрывающую особенности контроллера прерываний конкретной модели контроллера ARM. Этот уровень абстракции необходим для отделения универсального интерфейса обработчика прерываний "ARM_irq()" от конкретных особенностей аппаратуры, зависящих от производителя.

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

Покуда производители ARM-процессоров используют различные адреса регистров, управляющих контроллерами прерываний, невозможно проводить векторизацию единообразным способом. (Конечно, ценой потери универсальности вы можете сократить избыточность функции "BSP_irq()", перенеся её обязанности непосредственно в обработчик "ARM_irq()").

(Примечание. фирма ARM Limited стандартизировала интерфейс контроллера прерываний начиная с архитектуры ARM v7-M, содержащей стандартный контроллер вложенных прерываний (standard Nested Interrupt Controller - NVIC) [14].)

 
Листинг 9.1 Функция "BSP_irq()" (isr.c)

(1)   __attribute__ ((section (".text.fastcode")))
(2)   void BSP_irq(void) {
(3)         typedef void (*IntVector)(void);

            /* read the IVR */
(4)         IntVector vect = (IntVector)AT91C_BASE_AIC->AIC_IVR;

            /* write IVR if AIC in protected mode */
(5)         AT91C_BASE_AIC->AIC_IVR = (AT91_REG)vect;

            /* allow nesting interrupts */
(6)         asm("MSR cpsr_c,#(0x1F)");

            /* call the IRQ handler via the pointer to function */
(7)         (*vect)();

            /* lock IRQ before return */
(8)         asm("MSR cpsr_c,#(0x1F | 0x80)");

            /* write AIC_EOICR to clear interrupt */
(9)         AT91C_BASE_AIC->AIC_EOICR = (AT91_REG)vect;
      }
										

Выше приведёна реализация функции "BSP_irq()" для контроллера AIC фирмы Atmel. Ниже поясняются ключевые моменты:

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

(2) "BSP_irq()" - написана не как обработчик прерываний, а как обычная функция на языке "Си". В момент передачи на неё управления прерывания IRQ запрещены, а FIQ - разрешены.

(3) Данный "typedef" объявляет указатель на функцию, используемый для хранения адреса обработчика прерывания (ISR), получаемого из приоритетного контроллера.

(4) Вектор текущего прерывания загружается из регистра "AIC_IVR" во временную переменную "vect". Отметим, что "BSP_irq()" сохраняет все возможности векторизации, предоставляемой контроллером AIC, даже при том, что не проводит автовекторизацию в традиционном смысле.

Для того, чтобы механизм автовекторизации работал, регистры контроллера (Регистры Векторов Источников - Source Vector Registers) должны быть инициализированы адресами соответствующих обработчиков прерываний (ISR).

(5) Производится цикл записи регистра "AIC_IVR", необходимый, если AIC настроен для работы в защищённом режиме (см. документацию фирмы Atmel [15]). Цикл записи запускает процедуру приоритизации текущего прерывания.

(6) После запуска приоритизации можно разрешать прерывания на уровне ядра ARM.

(Примечание. Для сброса бита "I" регистра CPSR используется ассемблерная инструкция "MSR", доступная только в наборе команд ARM. Это означает, что модуль, содержащий "BSP_irq()" должен компилироваться для набора инструкций ARM.)

(7) Обработчик прерывания вызывается через указатель на функцию (содержимое вектора), полученный ранее из "AIC_IVR".

(8) После того, как обработчик прерываний возвращает управление, IRQ прерывания запрещаются на уровне ядра.

(9) Команда "End-Of-Interrupt", записываемая в AIC, завершает процедуру приоритизации текущего прерывания.


9.2 Функция обработчика "BSP_fiq()"

Так же, как и большинство контроллеров прерываний, объединённый с ядром ARM, AIC не защищает линию FIQ приоритетным контроллером [15]. Таким образом, несмотря на то, что AIC может проводить векторизацию FIQ прерывания, смысла это действие не имеет. Покозанная ниже на листинге 9.2 реализация функции "BSP_fiq()" делает всю работу с прерыванием самостоятельно, не затрагивая AIC.

 
Листинг 9.2 Функция "BSP_fiq()" (isr.c)

(1)   __attribute__ ((section (".text.fastcode")))

      /* FIQ ISR*/
(2)   void BSP_fiq(void) {
            /* Handle the FIQ directly. No AIC vectoring overhead necessary */

            /* clear int soruce */
(3)         uint32_t volatile dummy = AT91C_BASE_TC1->TC_SR;

            /* for example, set an event flag */
(4)         eventFlagSet(TIMER1_FLAG);

            /* suppress warning: ""dummy" was set but never used" */
            (void)dummy;
      }
										

Ключевые моменты функции:

(1) Для кода функции "BSP_fiq()" назначается сегмент ".text.fastcode", который для увеличения быстродействия будет загружен компоновщиком [а на самом деле - стартовым кодом] в оперативной памяти (см. вторую часть статьи).

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

(3-4) Функция "BSP_fiq()" обрабатывает прерывания самостоятельно. В данном случае сбрасывает источник прерывания и устанавливает флаг, который может использоваться в других задачах и, возможно, прерываниях.

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

Обратитесь к коду функции, расположенной в файле "isr.c", и объяснениям по работе с критическими секциями из седьмой части статьи.


9.3 Подпрограммы обработки прерываний

Основная задача функции "BSP_irq()" - получение от контроллера прерываний адреса обработчика и передача на него управления. Обработчик - обычная функция на языке "Си" (без модификатора "IRQ"). Код можно компилировать под любой набор инструкций: ARM или THUMB. В листинге 9.3 показаны два примера обработчиков прерываний для проекта "Blinky", сопровождающего статью.

 
Листинг 9.3 Пример обработчиков прерывания (ISR)

      /* Programmable Interval Timer (PIT) ISR */
(1)   void ISR_pit(void) {

            /*clear int source */
(2)         uint32_t volatile dummy = AT91C_BASE_PITC->PITC_PIVR;

            /* set the PIT event flag */
(3)         eventFlagSet(PIT_FLAG);

            /* suppress warning "dummy" was set but never used */
            (void)dummy;
      }

      /* Timer0 ISR */
      void ISR_timer0(void) {

            /* clear int soruce */
            uint32_t volatile dummy = AT91C_BASE_TC0->TC_SR;

            /* set the TIMER0 event flag */
            eventFlagSet(TIMER0_FLAG);

            /* suppress warning "dummy" was set but never used */
            (void)dummy;
      }

      /* spurious ISR */
(4)   void ISR_spur(void) {
      }
										

Отметим основные моменты реализации:

(1) Обработчик прерывания написан как обычная функция уровня приложения "void (*)(void)" и вызывается в режиме SYSTEM с разрешёнными IRQ и FIQ прерываниями.

(2) Сбрасывается чувствительный к уровню источник прерывания. В данном случае это входящий в состав AT91 программируемый интервальный таймер (Programmable Interval Timer - PIT). Несмотря на то, что прерывания разрешены на уровне ядра ARM, они проходят процедуру приоритизации в контроллере прерываний и не могут вызвать рекурсивного вызова обработчика [контроллер пропустит только прерывания с более высоким приоритетом, нежели приоритет текущего прерывания].

(3) В данном случае задачей обработчика является активизаця флага, информирующего основной программный цикл "main()" о срабатывании таймера. Функция "eventFlagSet()" защищает доступ к общему битовому массиву критической секцией, необходимой из-за того, что IRQ прерывания могут перехватывать управление друг у друга (см. листинг 9.1(6) ).

Отметим, что контроллер прерываний разрешает передачу управления обработчику прерываний более высокого приоритета, нежели у активного в данный момент. Таким образом, функция "ISR_tick()" не может перехватить управление у самой себя [приоритеты равны].

(4) Вместо обработчика ложных прерываний поставлена заглушка.

(Примечание. Ложные прерывания возможны в контроллерах на ядре ARM7/ARM9 из-за того, что аппаратная обработка прерываний асинхронна по отношению к системному тактированию. Ложными называются внешние события, которые воздействовали на входную линию контроллера прерываний достаточно долго для фиксации факта прерывания, но к моменту чтения регистра вектора прерывания успели перейти в неактивное состояние. Информационные материалы фирмы Atmel [15] и NXP [16] содержат дополнительную информацию о ложных прерываниях в контроллерах на базе ядра ARM.)


9.4 Инициализация таблицы векторов и контроллера прерываний

Вся стратегия обработки прерываний базируется на правильной инициализации таблицы векторов и контроллера прерываний. В сопровождающем статью коде эта задача выполняется функцией "BSP_init()", расположенной в файле "bsp.c".

 
Листинг 9.4 Инициализация таблицы векторов и контроллера прерываний

      #define ISR_PIT_PRIO     (AT91C_AIC_PRIOR_LOWEST + 0)

      ...

(1)   void BSP_init(void) {
            uint32_t i;

            ...

            /* hook the exception handlers */
(2)         *(uint32_t volatile *)0x24 = (uint32_t)&ARM_undef;
(3)         *(uint32_t volatile *)0x28 = (uint32_t)&ARM_swi;
(4)         *(uint32_t volatile *)0x2C = (uint32_t)&ARM_pAbort;
(5)         *(uint32_t volatile *)0x30 = (uint32_t)&ARM_dAbort;
(6)         *(uint32_t volatile *)0x34 = (uint32_t)&ARM_reserved;
(7)         *(uint32_t volatile *)0x38 = (uint32_t)&ARM_irq;
(8)         *(uint32_t volatile *)0x3C = (uint32_t)&ARM_fiq;

            /* configure Advanced Interrupt Controller (AIC) of AT91... */
            /* disable all interrupts */
            AT91C_BASE_AIC->AIC_IDCR = ~0;

            /* clear all interrupts */
            AT91C_BASE_AIC->AIC_ICCR = ~0;
            for (i = 0; i < 8; ++i) {
                  /* write AIC_EOICR 8 times */
                  AT91C_BASE_AIC->AIC_EOICR = 0;
            }

            /* spurious ISR */
(10)        AT91C_BASE_AIC->AIC_SPU  = (AT91_REG)&ISR_spur;

            /* configure the PIT interrupt */
            i = (MCK / 16 / BSP_TICKS_PER_SEC) - 1;
            AT91C_BASE_PITC->PITC_PIMR = (AT91C_PITC_PITEN | AT91C_PITC_PITIEN | i);

            /* PIT ISR */
(9)         AT91C_BASE_AIC->AIC_SVR[AT91C_ID_SYS] = (AT91_REG)&ISR_pit;
(11)        AT91C_BASE_AIC->AIC_SMR[AT91C_ID_SYS] =
                  (AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL | ISR_PIT_PRIO);
            AT91C_BASE_AIC->AIC_ICCR = (1 << AT91C_ID_SYS);
            AT91C_BASE_AIC->AIC_IECR = (1 << AT91C_ID_SYS);

            ...

            /* unlock IRQ/FIQ at the ARM core level */
(12)        ARM_INT_UNLOCK(0x1F);
      }
										

Отметим основные моменты инициализации:

(1) Функция "BSP_init()" вызывается из "main()" для инициализации аппаратуры отладочной платы. В момент вызова "BSP_init()" IRQ и FIQ прерывания запрещены.

(2-8) Таблица переадресации, начинающаяся с адреса 0x00000020 заполняется адресами низкоуровневых ассемблерных обработчиков исключений (Часть 2 данной публикации описывает таблицу векторов ARM и таблицу переадресации). Все низкоуровневые ассемблерные обработчики располагаются в файле "arm_exc.s" из состава сопровождающего статью кода. Исключения ARM будут кратко рассмотрены далее.

(7-8) В частности, по адресу 0x00000038 ((0x04 * 6)+ 0x20) таблицы переадресации располагается адрес обработчика "ARM_irq()", а следующий адрес 0x0000003C отводится под ссылку на "ARM_fiq()", обсуждавшиеся в части 8 статьи.

(9) В регистр вектора источника (Source Vector Register - AIC_SVR) для системного времени (PIT) записывается адрес "ISR_pit()" (см. листинг 9.3 ).

(10) В регистр вектора ложного прерывания (Spurious Interrupt Vector Register - AIC_SPU) записывается адрес "ISR_spur()" (см. листинг 9.3 ).

(11) В AIC выставляется приоритет прерывания системного таймера.

(12) После завершения инициализации таблицы векторов и AIC, прерывания должны быть разрешены на уровне ядра ARM (см. седьмую часть статьи).


9.5 Обработчики для остальных исключений

В листинге 9.4 функция "BSP_init()" инициализировала вторую таблицу с адресами обработчиков исключений ARM, такими, как "ARM_und()" (Undefined Instruction), "ARM_swi()" (Software Interrupt), "ARM_pAbort()" (Prefetch Abort) и "ARM_dAbort()" (Data Abort).

Эти низкоуровневые обработчики исключений определены в файле "arm_exc.c". Все они являются заготовками и обеспечивают минимальную функциональность, достаточную для простых проектов.

 
Листинг 9.5 Заготовка для обработчика исключений

            .global ARM_undef
            .func   ARM_undef

(1)   ARM_undef:
(2)         LDR     r0,Csting_undef
(3)         B       ARM_except
            ...

            .global ARM_dAbort
            .func   ARM_dAbort

(4)   ARM_dAbort:
            LDR     r0,Csting_dAbort
            B       ARM_except
            ...

(5)   ARM_except:
            /* set line number to the exception address */
(6)         SUB     r1,lr,#4

            /* SYSTEM mode, IRQ/FIQ disabled */
(7)         MSR     cpsr_c,#(SYS_MODE | NO_INT)
(8)         LDR     r12,=BSP_abort

            /* store the return address */
(9)         MOV     lr,pc

            /* call the assertion-handler (ARM/THUMB) */
(10)        BX      r12

            /* the assertion handler should not return, but in case it does
            * hang up the machine in the following endless loop
            */
(11)        B       .

            ...

(12)  Csting_undef:  .string  "Undefined"
            ...

(13)  Csting_dAbort: .string  "Data Abort"
            ...
										

Как показано в листинге 9.5 каждый обработчик (такой, как "ARM_undef()" или "ARM_dAbort()") загружает в регистр "r0" адрес строки, описывающей исключение, после чего передаёт управление на единую для всех часть - "ARM_except()".

Общая часть записывает в регистр "r1" адрес возврата из исключения, переключается в режим SYSTEM и вызывает "Си"-функцию "BSP_abort()". Зависящая от аппаратуры отладочной платы функция "BSP_abort()" пытается сообщить об исключении (информация о котором содержится во входных аргументах функции - регистрах "r0" и "r1"), переводит устройство в безопасный режим и, возможно, сбрасывает его.

Данная функция не должна возвращать управление, так как в простом проекте без операционной системы его возвращать некуда. "BSP_abort()" - хорошее место для постоянной точки останова.

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