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


Часть 6

В этой части затрагивается вопрос обработки прерываний в простой программной конфигурации из приоритетного и фонового процессов без многозадачной операционной системы (bare-metal). Предлагаемая методика обработки включает полную поддержку вложеных прерываний и может использовать аппаратный контроллер прерываний при его наличии.

Здесь описываются общие вопросы обработки прерываний. Методы блокировки прерываний, код обработчиков на языке "Си" и их ассемблерных оболочек, и, наконец, принципы тестирования системы прерываний для процессоров ARM будут представлены позднее.

Рекомендуется ознакомиться с технической заметкой фирмы ARM "Writing Interrupt Handlers" [8], заметкой по применению фирмы Philips AN10381 "Nesting of Interrupts in the LPC2000" [9] и документом фирмы Atmel "Interrupt Management: Auto-vectoring and Prioritization" [10].


6.1 Обработка прерываний - описание проблемы

Вычислительное ядро ARM поддерживает два типа прерываний: запрос прерывания (Interrupt Request - IRQ), быстрый запрос прерывания (Fast Interrupt Request - FIQ) и несколько исключений: Undefined Instruction, Prefetch Abort, Data Abort и Software Interrupt. При вхождении ядра ARM в прерывание или исключение автоматического сохранения каких-либо регистров в стеке не производится.

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

GNU gcc предоставляет атрибут "__attribute__ ((interrupt ("IRQ")))" для указания на то, что отмеченная C/C++ функция является обработчиком IRQ прерываний (аналогично, атрибут "__attribute__ ((interrupt ("FIQ")))" используется для обработчиков FIQ прерываний).

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

В тоже самое время, большинство процессоров ARM имеют приоритетный контроллер, предназначенный для поддержки вложенности и приоритизации прерываний от нескольких источников. Но это мощное средство не может использоваться, если программное обеспечение вложенности прерываний не поддерживает.


6.2 Стратегия обработки прерываний

Для того, чтобы разрешить вложенность обработчик должен отменить блокировку прерываний, автоматически активированную ядром ARM в момент входа в IRQ/FIQ прерывание. В общем случае, все документированные методы обработки вложенных прерываний для архитектуры ARM подразумевают, что перед разрешением прерываний производится переключение режима ядра из IRQ (или FIQ) в режим, используемый прикладным кодом [8, 9, 10].

Стандартная техника использует в процессе обработки несколько стеков. Стек режима IRQ/FIQ используется для сохранения части контекста, а стек режима SYSTEM/USER (иногда SVC) - для сохранения всего остального. Фирма ARM Ltd. рекомендует использование режима SYSTEM для написания повторно входимых обработчиков прерываний [8].

Описываемая ниже методика обработки для простых ARM-систем тоже использует переключение из режима IRQ/FIQ в режим SYSTEM, перед тем как разрешить прерывания, но отличается от других схем в которых весь контекст сохраняется в SYSTEM/USER стеке, а IRQ/FIQ стек не используется вовсе.

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

 

 
Рисунок 6.1 Общие принципы обработки прерываний с использованием аппаратного контроллера
Samek_bare_metal_Part6Fig1.jpg

Рисунок 6.1 демонстрирует этапы обработки IRQ прерываний. Процесс начинается в момент распознания сигнала IRQ. Ядро ARM переключается в режим IRQ (CPSR[0-4]==0x12), а в указатель инструкций - PC - загружается значение 0x00000018.

Во второй части говорилось о том, что в таблице векторов по адресу 0x00000018 была размещена инструкция "LDR pc, [pc, #0x18]".

Это инструкция загружает в PC содержимое ячейки из второй таблицы по адресу 0x00000020 + 0x18 = 0x00000038, в которой должен быть предварительно прописан адрес обработчика "ARM_irq()" - ассемблерной оболочки, которая будет детально описана в восьмой части статьи.

Сейчас даётся только общее описание процесса, а подробности реализации "ARM_irq()" опускаются. Сообщим лишь, что "ARM_irq()" сохраняет все необходимые регистры и переключается из режима IRQ в режим SYSTEM (CPSR[0-4]==0x1F). Ассемблерная оболочка функции "ARM_irq()" универсальна и не требует никакой модификации под различные модели контроллеров ARM.

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

"BSP_irq()" может быть написана на языке "Си", как обычная функция [без атрибутов компиляции, свойственных обработчикам прерываний], и вызываться в режиме SYSTEM так же, как и обычная функция приложения. Следует отметить, что вызывается "BSP_irq()" с заблокированными IRQ прерываниями и разрешённым FIQ прерыванием, то есть в том состоянии, в котором биты подсистемы прерываний были в момент входа в режим IRQ.

В случае отсутствия контроллера прерываний вы можете обрабатывать IRQ прерывания непосредственно в "BSP_irq()". Основная рекомендация: в случае отсутствия аппаратного контроллера разрешать IRQ прерывание в обработчике нельзя, так как отсутствует аппаратный механизм блокировки повторного вхождения в обработчик при возникновении нового сигнала IRQ.

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

При наличии приоритетного контроллера процедура становится чуть более запутанной (см. рисунок 6.1). Первым делом функция "BSP_irq()" считывает вектор текущего прерывания из контроллера.

Цикл чтения вектора запускает процедуру приоритизации текущего прерывания в контроллере. Таким образом, после считывания вектора разрешение всех прерываний (например, инструкцией "MSR cpsr_c, #0x1F") совершенно безопасно.

Затем "BSP_irq()" вызывает обработчик по вектору, используя синтаксис указателя на функцию ( (*vect)() ). Для того, чтобы приведённая конструкция работала, приоритетный контроллер должен быть инициализирован адресами обработчиков прерываний (ISR), как показано в нижней части рисунка 6.1.

После возврата управления из обработчика, "BSP_irq()" блокирует IRQ и FIQ прерывания (например, ассемблерной инструкцией "MSR cpsr_c, #(0x1F | 0x80 | 0x40)"). Наконец, "BSP_irq()" возвращает механизм приоритизации контроллера в исходное состояние записью инструкции "конец цикла прерывания" (End-Of-Interrupt - EOI).

Прилагаемый к статье код демонстрирует пример функции "BSP_irq()" для усовршенствованного контроллера прерываний (Advanced Interrupt Controller - AIC) фирмы Atmel. Другие контроллеры имеют иные имена и адреса регистров, но работают аналогично.

Примечание. Если вы используете ассемблерную команду "MSR cpsr_c, #0x1F" для разрешения и "MSR cpsr_c, #(0x1F | 0x80)" для блокировки прерываний, то фукция "BSP_irq()" должна компилироваться под набор инструкций ARM, так как в наборе команд THUMB такой код операции отсутствует.

"BSP_irq()" возвращает управление непосредственно в универсальную ассемблерную оболочку "ARM_irq()" (средняя секция рисунка 6.1). "ARM_irq()" восстанавливает контекст из стека режима SYSTEM, переключает режим обратно в IRQ и выполняет стандартный выход из исключения инструкцией "MOVS pc, lr".


6.3 Обработка FIQ

Обработка FIQ прерываний очень похожа на обработку IRQ во всём, что касается ассемблерной оболочки и инициализации таблицы векторов. Основное отличие от IRQ состоит с том, что линия прерывания FIQ обычно не обрабатывается приоритетным контроллером (таким, как Atmel AIC, NXP VIC), как показано на рисунке 6.2.

 
Рисунок 6.2 Типовая система на процессоре ARM с контроллером прерываний
Samek_bare_metal_Part6Fig2.jpg

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

Другими словами, несмотря на то, что приоритетные контроллеры большинства популярных процессоров ARM поддерживают возможность векторизации для FIQ, использование её бессмысленно. Во-вторых, гораздо важнее то, что вы не можете разрешать FIQ и IRQ прерывания в процессе обработки FIQ.

После разрешение FIQ и/или IRQ обработка текущего прерывания FIQ становится уязвимой для перехвата управления новым сигналом IRQ или FIQ (с возможным повторением последовательности на следующем витке). Оба варианта демонстрируют инверсию приоритета [то есть низкоприоритетное прерывание IRQ перехватывает управление у высокоприоритетного прерывания FIQ].

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


6.4 Отсутствие автовекторизации

Возможно, следует подчеркнуть, что представляемая здесь методика обработки прерываний не использует возможности автовекторизации, описанные в примерах по применению [9] и [10]. Автовекторизация происходит при выполнении инструкции "LDR", расположенной по адресу 0x00000018, для IRQ (данный пример относится к AIC фирмы Atmel):

 

    ORG 0x18
    LDR pc,[pc,#-0xF20]
										

При возникновении прерывания ядро ARM загружает в PC значение 0x00000018, что вызывает выполнение расположенной по адресу 0x00000018 инструкции "LDR pc, [pc, #-0xF20]". Эффективный адрес вычисляемый в инструкции равен 0x00000020 + (-0xF20) = 0xFFFFF100 (число 0x20 - это значение PC в момент исполнения инструкции по адресу 0x00000018, обусловленное конвейером команд ядра ARM).

В результате вычислительное ядро ARM загружает в PC значение, выбранное из расположенного по адресу 0xFFFFF100 регистра "AIC_IVR". Цикл чтения, в свою очередь, заставляет приоритетный контроллер возвратить через "AIC_IVR" адрес обработчика активного прерывания (об инициализации которого говорилось в предыдущей части статьи).

Таким образом, единственная инструкция "LDR pc, [pc, #-0xF20]" запускает процесс приоритизации активного прерывания и вызывает переход по адресу соответствующего обработчика, каковой переход называется "автовекторизацией" ("auto-vectoring").

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

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

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

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