Lockup (блокировка ядра Cortex-M)
Отказ в обычном коде ведёт в обработчик HardFault. А если отказ случился в самом HardFault или в NMI, обрабатывать его уже нечем. Тогда ядро Cortex-M останавливается в особом состоянии — lockup — и ждёт внешней помощи.
Обновлено Редакция GAW
Lockup — состояние ядра Cortex-M, в которое оно уходит, когда отказ случился там, где его некому обработать: во время выполнения обработчика HardFault или NMI. В lockup ядро не выполняет команды и остаётся в нём до сброса, прерывания NMI или остановки отладчиком (DUI 0552A, разд. 2.4.4).
Почему это происходит
Обычный отказ ведёт в обработчик с более высоким приоритетом, в крайнем случае — в HardFault с приоритетом −1. Но на приоритете −1 и выше архитектура считает любой отказ или вызов SVC фатальным: стандартный вход в исключение здесь не применяется, и большинство таких случаев приводит к lockup (ARMv7-M ARM, B1.5.15). Типичные причины:
- отказ в самом обработчике HardFault — например, он читает данные через испорченный указатель стека;
- ошибка чтения таблицы векторов при сбросе или при входе в HardFault или NMI — неверный адрес таблицы или стёртая flash;
- переход по адресу с нулевым младшим битом, но только внутри обработчика HardFault или NMI. В обычном коде такой переход даёт UsageFault INVSTATE, а при выключенном UsageFault — HardFault: DUI 0552A говорит, что выполнение команд при T = 0 ведёт «к отказу или lockup» (разд. 2.1.3, табл. 4-27); в lockup превращается только отказ, которому уже некуда эскалироваться;
- ошибка шины данных внутри HardFault или NMI при CCR.BFHFNMIGN = 0 (табл. 4-20).
Что видно снаружи
В lockup ядро многократно выбирает одну команду с фиксированного адреса: при BusFault на выборке — адрес этой команды, при ошибке чтения векторов — 0xFFFFFFFF в самом конце адресного пространства. Ядро ставит бит S_LOCKUP в регистре DHCSR и биты в регистрах отказов, соответствующие причине (B1.5.15, табл. B1-10). Почему отладчик в таком случае показывает PC = 0xFFFFFFFE, разобрано в ответе о симптомах lockup.
Выход
| Способ | Работает |
|---|---|
| Сброс | всегда |
| Остановка отладчиком | всегда |
| NMI | только если lockup случился в обработчике HardFault |
Архитектура допускает и самоустранение — если ошибка шины была временной, — но Arm прямо пишет, что lockup следует считать конечным состоянием, и обычно ядро остаётся в нём до сброса, например от сторожевого таймера (B1.5.15). Поэтому в готовом устройстве сторожевой таймер оставляют включённым: другого способа самостоятельно вернуться к работе у кристалла нет. Обработку отказов ядра в целом описывает статья «Cortex-M3».
Частые вопросы
Чем lockup отличается от зависания в HardFault_Handler?
В HardFault_Handler ядро работает: оно крутит бесконечный цикл обработчика, и в нём можно поставить точку останова и прочитать регистры отказов. В lockup ядро не выполняет команды вовсе — оно повторно выбирает одну и ту же команду с фиксированного адреса. Отладчик видит остановленное ядро с битом S_LOCKUP в регистре DHCSR.
Как выйти из lockup?
Сбросом, остановкой отладчиком или — если lockup случился в обработчике HardFault — прерыванием NMI. Lockup внутри обработчика NMI следующий NMI уже не снимает. На практике выводит из него только сброс, поэтому в устройстве оставляют включённый сторожевой таймер.
Как не попасть в lockup?
Держать обработчики HardFault и NMI короткими и не трогать в них сомнительные указатели и стек. Хранить в таблице векторов правильные адреса с единицей в младшем бите. Не включать BFHFNMIGN, если не уверены в памяти. И оставить сторожевой таймер, который сбросит кристалл, если ядро всё же заблокировалось.
Источники
- Arm. Cortex-M3 Devices Generic User Guide, DUI 0552A, 2010 — разд. 2.4.4: ядро входит в lockup при отказе во время выполнения обработчиков NMI или HardFault; в lockup команды не выполняются; выход — сброс, NMI или остановка отладчиком; если lockup случился в обработчике NMI, следующий NMI не выводит из него; разд. 2.1.3: выполнение команд при T = 0 ведёт к отказу или lockup; табл. 4-20: CCR.BFHFNMIGN = 0 — ошибки шины данных в обработчиках с приоритетом −1 и −2 вызывают lockup, 1 — игнорируются, ставить только для заведомо безопасной памяти
- Arm. ARMv7-M Architecture Reference Manual, ARM DDI 0403C_errata_v3, 2010 — B1.5.15 Unrecoverable exception cases: на приоритете −1 и выше отказы и SVC считаются фатальными; в lockup ядро многократно выбирает команду с фиксированного адреса, ставит бит S_LOCKUP в DHCSR и соответствующие биты регистров отказов; рекомендуется внешний сигнал о lockup; выход — NMI при lockup в HardFault, системный сброс, остановка отладчиком, самоустранение ошибки памяти; обычно ядро остаётся в lockup до сброса, например от сторожевого таймера; табл. B1-10: ошибка чтения таблицы векторов при сбросе, входе в NMI или HardFault — адрес lockup 0xFFFFFFFF
- Arm. Cortex-M3 Processor Technical Reference Manual, revision r2p1 (ARM 100165), 2016 — разд. 3.9.2: исключение при сохранении контекста из HardFault в NMI ведёт в lockup на приоритете NMI, при восстановлении из NMI в HardFault — на приоритете HardFault
- Arm. Cortex-M0 Devices Generic User Guide, DUI 0497A, 2009 — разд. 2.4.1: у Cortex-M0 lockup также при ошибке шины при восстановлении PSR из основного стека на возврате из исключения; выход — сброс, отладчик или NMI, если lockup в обработчике HardFault
Нашли ошибку?
Смотрите также
- Почему отладчик показывает PC = 0xFFFFFFFE и ядро Cortex-M не работает?Отказ в HardFault или неверная таблица векторов: ядро в lockup до сброса
- HardFaultИсключение Cortex-M при необработанной ошибке
- NMI (немаскируемое прерывание)Исключение Cortex-M с приоритетом −2, которое нельзя запретить
- Сторожевой таймерWatchdog: сброс МК при зависании программы
- Cortex-M3Регистры, карта памяти, bit-banding, NVIC, SysTick, MPU, старт, пример