Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Nested interrupts and Hard Fault
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
xelax
Исходные данные.
Код для cortex-m4 (nrf52), язык С, компилятор Keil.

Ситуация следующая. В принципе все ок и работает. Работаем в основном с GCC, но по условиям ТЗ код должен собираться и работать также на IAR и Keil, так что периодически билдим и запускаем код на них.
GCC и IAR работают без проблем. Keil падает в hard fault в рандомном месте. Первые подозрения были на проезды по памяти. Детальное исследование одного из падений показало, что hard fault случается так как портится значение в регистре общего назначения r5, который используется компилятором внутри функции как базовый адрес для работы с периферией. Внутри функции r5 не обновляется (записывается константа при входе в функцию один раз). То есть предположение проезда по памяти отпадает.
Второе предположение что поведение ломают вложенные прерывания подтвердилось. Сделали все прерывания одинакового приоритета hard fault исчез.

Вроде как баг исчез и стоит порадоваться, но очень похоже на черную магию, так как логического объяснения такому поведению не можем дать. Сохранение регистров в стек и восстановление везде присутствует (как минимум тщательно проверил сохранение\восстановление контекста в функции где упал в hard fault и тела высокоприоритетного прерывания).

Кто-нибудь может дать теоретически обоснованную модель такому поведению, чтобы понять куда еще стоит посмотреть?
jcxz
Цитата(xelax @ Jun 21 2016, 19:08) *
Кто-нибудь может дать теоретически обоснованную модель такому поведению, чтобы понять куда еще стоит посмотреть?

Посмотрите размер стека прерываний.
Ассемблерные вставки есть?
xelax
Сразу хочу извиниться дизасм сделал гнусным objdump, так как кейловским fromelf пользоваться не очень умею. В принципе разницы быть не должно, так как исходный файл в elf формате.
hard fault был после вызова по адресу 1324c, так как в r0 была ересь, которая попала туда из r5.
Самое примечательное, что до той строчки внутри функции все было ок, то есть в r5 ереси не было. Сама функция uart_irq_handler является прерывание, но с низким приоритетом, то есть ее вытеснить и испоганить r5 могло только прерывание с высоким приоритетом. r5 нигде не пишется кроме адреса 13174.

Сам код собственно даже не наш, а взят из SDK Nordic.

CODE
#if defined(UART_IN_USE)
__STATIC_INLINE void uart_irq_handler()
{
13170: e92d 4bff stmdb sp!, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, fp, lr}
if (nrf_uart_int_enable_check(NRF_UART0, NRF_UART_INT_MASK_ERROR) &&
13174: 4d43 ldr r5, [pc, #268]; (13284 <uart_irq_handler+0x114>)
}


#if defined(UART_IN_USE)
__STATIC_INLINE void uart_irq_handler()
{
13176: f10d 0b2c add.w fp, sp, #44; 0x2c
if (nrf_uart_int_enable_check(NRF_UART0, NRF_UART_INT_MASK_ERROR) &&
1317a: f44f 7100 mov.w r1, #512; 0x200
1317e: 4628 mov r0, r5
13180: f7fc f920 bl f3c4 <nrf_uart_int_enable_check>
13184: 2701 movs r7, #1
nrf_uart_event_check(NRF_UART0, NRF_UART_EVENT_ERROR))
{
nrf_drv_uart_event_t event;
nrf_uart_event_clear(NRF_UART0, NRF_UART_EVENT_ERROR);
nrf_uart_int_disable(NRF_UART0, NRF_UART_INT_MASK_RXDRDY | NRF_UART_INT_MASK_ERROR);
if (!m_cb.rx_enabled)
13186: 4c40 ldr r4, [pc, #256]; (13288 <uart_irq_handler+0x118>)
if (nrf_uart_int_enable_check(NRF_UART0, NRF_UART_INT_MASK_ERROR) &&
nrf_uart_event_check(NRF_UART0, NRF_UART_EVENT_ERROR))
{
nrf_drv_uart_event_t event;
nrf_uart_event_clear(NRF_UART0, NRF_UART_EVENT_ERROR);
nrf_uart_int_disable(NRF_UART0, NRF_UART_INT_MASK_RXDRDY | NRF_UART_INT_MASK_ERROR);
13188: f44f 7801 mov.w r8, #516; 0x204
1318c: 2600 movs r6, #0


#if defined(UART_IN_USE)
__STATIC_INLINE void uart_irq_handler()
{
if (nrf_uart_int_enable_check(NRF_UART0, NRF_UART_INT_MASK_ERROR) &&
1318e: b318 cbz r0, 131d8 <uart_irq_handler+0x68>
nrf_uart_event_check(NRF_UART0, NRF_UART_EVENT_ERROR))
13190: f44f 7192 mov.w r1, #292; 0x124
13194: 4628 mov r0, r5
13196: f7fc f907 bl f3a8 <nrf_uart_event_check>
1319a: b1e8 cbz r0, 131d8 <uart_irq_handler+0x68>
1319c: f8c5 6124 str.w r6, [r5, #292]; 0x124
131a0: f8c5 8308 str.w r8, [r5, #776]; 0x308
{
nrf_drv_uart_event_t event;
nrf_uart_event_clear(NRF_UART0, NRF_UART_EVENT_ERROR);
nrf_uart_int_disable(NRF_UART0, NRF_UART_INT_MASK_RXDRDY | NRF_UART_INT_MASK_ERROR);
if (!m_cb.rx_enabled)
131a4: 7ea0 ldrb r0, [r4, #26]
131a6: b900 cbnz r0, 131aa <uart_irq_handler+0x3a>
131a8: 606f str r7, [r5, #4]
{
nrf_uart_task_trigger(NRF_UART0, NRF_UART_TASK_STOPRX);
}
event.type = NRF_DRV_UART_EVT_ERROR;
131aa: 2002 movs r0, #2
131ac: f80b 0c2c strb.w r0, [fp, #-44]
131b0: f8d5 0480 ldr.w r0, [r5, #1152]; 0x480
131b4: f8c5 0480 str.w r0, [r5, #1152]; 0x480
event.data.error.error_mask = nrf_uart_errorsrc_get_and_clear(NRF_UART0);
131b8: f84b 0c20 str.w r0, [fp, #-32]
event.data.error.rxtx.bytes = m_cb.rx_buffer_length;
131bc: 7de0 ldrb r0, [r4, #23]
131be: f80b 0c24 strb.w r0, [fp, #-36]
event.data.error.rxtx.p_data = m_cb.p_rx_buffer;
131c2: 68e0 ldr r0, [r4, #12]
131c4: f84b 0c28 str.w r0, [fp, #-40]

//abort transfer
m_cb.rx_buffer_length = 0;
131c8: 75e6 strb r6, [r4, #23]
m_cb.rx_secondary_buffer_length = 0;
131ca: 7626 strb r6, [r4, #24]

m_cb.handler(&event,m_cb.p_context);
131cc: e9d4 1200 ldrd r1, r2, [r4]
131d0: f1ab 002c sub.w r0, fp, #44; 0x2c
131d4: 4790 blx r2
}
131d6: e024 b.n 13222 <uart_irq_handler+0xb2>
else if (nrf_uart_int_enable_check(NRF_UART0, NRF_UART_INT_MASK_RXDRDY) &&
131d8: 2104 movs r1, #4
131da: 4628 mov r0, r5
131dc: f7fc f8f2 bl f3c4 <nrf_uart_int_enable_check>
131e0: b1f8 cbz r0, 13222 <uart_irq_handler+0xb2>
nrf_uart_event_check(NRF_UART0, NRF_UART_EVENT_RXDRDY))
131e2: f44f 7184 mov.w r1, #264; 0x108
131e6: 4628 mov r0, r5
131e8: f7fc f8de bl f3a8 <nrf_uart_event_check>
131ec: b1c8 cbz r0, 13222 <uart_irq_handler+0xb2>
{
rx_byte();
131ee: f7fe f89b bl 11328 <rx_byte>
if (m_cb.rx_buffer_length == m_cb.rx_counter)
131f2: 7de0 ldrb r0, [r4, #23]
131f4: 7e61 ldrb r1, [r4, #25]
131f6: 4288 cmp r0, r1
131f8: d113 bne.n 13222 <uart_irq_handler+0xb2>
{
if (m_cb.rx_secondary_buffer_length)
131fa: 7e22 ldrb r2, [r4, #24]
131fc: b13a cbz r2, 1320e <uart_irq_handler+0x9e>
{
uint8_t * p_data = m_cb.p_rx_buffer;
uint8_t rx_counter = m_cb.rx_counter;
131fe: 7e60 ldrb r0, [r4, #25]
13200: 68e1 ldr r1, [r4, #12]

//Switch to secondary buffer.
m_cb.rx_buffer_length = m_cb.rx_secondary_buffer_length;
13202: 75e2 strb r2, [r4, #23]
m_cb.p_rx_buffer = m_cb.p_rx_secondary_buffer;
13204: 6922 ldr r2, [r4, #16]
m_cb.rx_secondary_buffer_length = 0;
13206: 60e2 str r2, [r4, #12]
13208: 7626 strb r6, [r4, #24]
m_cb.rx_counter = 0;
1320a: 7666 strb r6, [r4, #25]
rx_done_event(rx_counter, p_data);
}
1320c: e007 b.n 1321e <uart_irq_handler+0xae>
else
{
if (!m_cb.rx_enabled)
1320e: 7ea0 ldrb r0, [r4, #26]
13210: b900 cbnz r0, 13214 <uart_irq_handler+0xa4>
13212: 606f str r7, [r5, #4]
13214: f8c5 8308 str.w r8, [r5, #776]; 0x308
{
nrf_uart_task_trigger(NRF_UART0, NRF_UART_TASK_STOPRX);
}
nrf_uart_int_disable(NRF_UART0, NRF_UART_INT_MASK_RXDRDY | NRF_UART_INT_MASK_ERROR);
m_cb.rx_buffer_length = 0;
13218: 75e6 strb r6, [r4, #23]
rx_done_event(m_cb.rx_counter, m_cb.p_rx_buffer);
1321a: 7e60 ldrb r0, [r4, #25]
1321c: 68e1 ldr r1, [r4, #12]
//Switch to secondary buffer.
m_cb.rx_buffer_length = m_cb.rx_secondary_buffer_length;
m_cb.p_rx_buffer = m_cb.p_rx_secondary_buffer;
m_cb.rx_secondary_buffer_length = 0;
m_cb.rx_counter = 0;
rx_done_event(rx_counter, p_data);
1321e: f7fe f8a3 bl 11368 <rx_done_event>
rx_done_event(m_cb.rx_counter, m_cb.p_rx_buffer);
}
}
}

if (nrf_uart_event_check(NRF_UART0, NRF_UART_EVENT_TXDRDY))
13222: f44f 718e mov.w r1, #284; 0x11c
13226: 4628 mov r0, r5
13228: f7fc f8be bl f3a8 <nrf_uart_event_check>
1322c: b158 cbz r0, 13246 <uart_irq_handler+0xd6>
{
if (m_cb.tx_counter < (uint16_t) m_cb.tx_buffer_length)
1322e: 8aa1 ldrh r1, [r4, #20]
13230: 7da0 ldrb r0, [r4, #22]
13232: 4281 cmp r1, r0
13234: d202 bcs.n 1323c <uart_irq_handler+0xcc>
{
tx_byte();
13236: f7ff fea1 bl 12f7c <tx_byte>
1323a: e004 b.n 13246 <uart_irq_handler+0xd6>
1323c: f8c5 611c str.w r6, [r5, #284]; 0x11c
}
else
{
nrf_uart_event_clear(NRF_UART0, NRF_UART_EVENT_TXDRDY);
if (m_cb.tx_buffer_length)
13240: b108 cbz r0, 13246 <uart_irq_handler+0xd6>
{
tx_done_event(m_cb.tx_buffer_length);
13242: f7ff feb5 bl 12fb0 <tx_done_event>
}
}
}

if (nrf_uart_event_check(NRF_UART0, NRF_UART_EVENT_RXTO))
13246: f44f 71a2 mov.w r1, #324; 0x144
1324a: 4628 mov r0, r5
1324c: f7fc f8ac bl f3a8 <nrf_uart_event_check>
13250: 46dd mov sp, fp
13252: 2800 cmp r0, #0
13254: b08b sub sp, #44; 0x2c
13256: d013 beq.n 13280 <uart_irq_handler+0x110>
13258: f8c5 6144 str.w r6, [r5, #324]; 0x144
{
nrf_uart_event_clear(NRF_UART0, NRF_UART_EVENT_RXTO);

// RXTO event may be triggered as a result of abort call. In th
if (m_cb.rx_enabled)
1325c: 7ea0 ldrb r0, [r4, #26]
1325e: b100 cbz r0, 13262 <uart_irq_handler+0xf2>
13260: 602f str r7, [r5, #0]
{
nrf_uart_task_trigger(NRF_UART0, NRF_UART_TASK_STARTRX);
}
if (m_cb.rx_buffer_length)
13262: 7de0 ldrb r0, [r4, #23]
13264: 46dd mov sp, fp
13266: 2800 cmp r0, #0
13268: b08b sub sp, #44; 0x2c
1326a: d009 beq.n 13280 <uart_irq_handler+0x110>
{
m_cb.rx_buffer_length = 0;
1326c: 75e6 strb r6, [r4, #23]
rx_done_event(m_cb.rx_counter, m_cb.p_rx_buffer);
1326e: 7e60 ldrb r0, [r4, #25]
13270: 68e1 ldr r1, [r4, #12]
13272: b004 add sp, #16
13274: e8bd 4bf0 ldmia.w sp!, {r4, r5, r6, r7, r8, r9, fp, lr}
13278: f7fe b876 b.w 11368 <rx_done_event>
1327c: 46dd mov sp, fp
1327e: b08b sub sp, #44; 0x2c
}
}
}
13280: e8bd 8bff ldmia.w sp!, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, fp, pc}
13284: 40002000 .word 0x40002000
13288: 200011d8 .word 0x200011d8


Цитата(jcxz @ Jun 21 2016, 16:19) *
Посмотрите размер стека прерываний.
Ассемблерные вставки есть?

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

Ассемблерные вставки не используем.
jcxz
Цитата(xelax @ Jun 21 2016, 19:50) *
Сразу хочу извиниться дизасм сделал гнусным objdump, так как кейловским fromelf пользоваться не очень умею. В принципе разницы быть не должно, так как исходный файл в elf формате.
13170: e92d 4bff stmdb sp!, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, fp, lr}
if (nrf_uart_int_enable_check(NRF_UART0, NRF_UART_INT_MASK_ERROR) &&
13174: 4d43 ldr r5, [pc, #268]; (13284 <uart_irq_handler+0x114>)
}


#if defined(UART_IN_USE)
__STATIC_INLINE void uart_irq_handler()
{
13176: f10d 0b2c add.w fp, sp, #44; 0x2c
...

Мусор это какой-то..... Что за регистр fp? Зачем на входе в ISR сохранять так много регистров?
И зачем Вы это привели? У Вас ISR-ы на асме написаны?

Цитата(xelax @ Jun 21 2016, 20:02) *
На стек 1024 байта выделили, так что по идее должно хватить, тем более модель памяти держим сходящуюся. Глобальные переменные с одного края памяти, стек с другого, так что даже при превышении размера стека, проедем по неиспользуемой памяти.

Я спрашивал не про "стек фонового процесса", а про "стек прерываний". У Cortex-M есть MSP и PSP. У Вас в каком режиме CPU фоновый процесс работает?
xelax
Цитата(jcxz @ Jun 21 2016, 17:05) *
Мусор это какой-то..... Что за регистр fp?

К сожалению objdump трактует r11 как fp.

Цитата(jcxz @ Jun 21 2016, 17:05) *
Зачем на входе в ISR сохранять так много регистров?

Не знаю, это Keil делает.

Цитата(jcxz @ Jun 21 2016, 17:05) *
И зачем Вы это привели?

Показать в каком коде происходит hard fault. Возможно разговор пойдет предметный, а не абстрактный.

Цитата(jcxz @ Jun 21 2016, 17:05) *
У Вас ISR-ы на асме написаны?

нет

Цитата(jcxz @ Jun 21 2016, 17:05) *
Я спрашивал не про "стек фонового процесса", а про "стек прерываний". У Cortex-M есть MSP и PSP. У Вас в каком режиме CPU фоновый процесс работает?

мы не переключаем в PSP, работаем в MSP. Ось не используем.
jcxz
Цитата(xelax @ Jun 21 2016, 20:22) *
мы не переключаем в PSP, работаем в MSP. Ось не используем.

Если нет асм-кода и нет переполнения стека, то возможно где-то есть разрушение переменных в стеке (и R5 восстанавливается из стека).
Если Вы нашли точку, где портится R5, то там и надо проверить.
romas2010
Цитата(xelax @ Jun 21 2016, 16:08) *
Исходные данные.
Код для cortex-m4 (nrf52), язык С, компилятор Keil.
.......
Второе предположение что поведение ломают вложенные прерывания подтвердилось. Сделали все прерывания одинакового приоритета hard fault исчез.
.....
Кто-нибудь может дать теоретически обоснованную модель такому поведению, чтобы понять куда еще стоит посмотреть?


Судя по приведенному ниже дизассемблеру, код кейл скомпилировал с опцией --use_frame_pointer. C этой опцией кейл генерит некорректный выход из функции..Попробуйте скомпилировать без нее...и еще нюанс-отключите оптимизацию,то есть --O0
xelax
Цитата(romas2010 @ Jun 21 2016, 19:37) *
Судя по приведенному ниже дизассемблеру, код кейл скомпилировал с опцией --use_frame_pointer. C этой опцией кейл генерит некорректный выход из функции..Попробуйте скомпилировать без нее...и еще нюанс-отключите оптимизацию,то есть --O0

Да мы действительно используем --use_frame_pointer. r11 как раз компилятором отводится под frame pointer. Можно поподробней о некорректном выходе из функции? есть какая-нибудь errata или ссылка на описание проблемы?
И оптимизацию зачем выключать? Мы без нее на финальном этапе проекта не влезем в размер памяти, отведенный нам по ТЗ.
xelax
Убрал опцию --use_frame_pointer, вернул вложенные прерывания, оставил максимальную оптимизацию --O3. Hard fault исчезли.
Вернул опцию, буквально несколько секунд и софт падает в Hard fault.

Спасибо, судя по всему, действительно идет некорректная работа со стеком с включенной опцией.
romas2010
Цитата(xelax @ Jun 22 2016, 17:12) *
Убрал опцию --use_frame_pointer, вернул вложенные прерывания, оставил максимальную оптимизацию --O3. Hard fault исчезли.
Вернул опцию, буквально несколько секунд и софт падает в Hard fault.

Спасибо, судя по всему, действительно идет некорректная работа со стеком с включенной опцией.


Фишка вот в чем
непонятно зачем кейл двигает указатель стека SP на FP в командах
mov sp,fp
sub sp,#const

Если между этими командами возникнет прерывание,стек будет уже смещен,и контекст возникшего прерывания затрет контекст,сохраненный ранее при входе..я понимаю,мои объяснения звучат сумбурно,но возьмите лист бумаги,разрисуйте стек,идите по коду,там,где есть в командах использование sp и fp, то "разрисовывайте" эти регистры,и вы увидите....учитывайте,что стек растет ото дна

stmdb sp!,{r0...lr} -> sp=sp-4;RAM[sp]=r0 и т.д.
ldmia sp!,{r0..lr}-> r0=RAM[sp]; sp=sp+4

Я вам скажу,это реальный глюк..я в свое время неделю не мог понять,что с моей программой,а потом матерился на кейл десятиэтажным матом
GetSmart
Цитата(xelax @ Jun 22 2016, 17:11) *
Да мы действительно используем --use_frame_pointer. r11 как раз компилятором отводится под frame pointer.

Зачем, если исходник не заточен строго под Кейл?

И в чём смысл STATIC_INLINE для обработчика прерывания? Как у кортексов, так и у других ядер ARM, обработчик всегда есть независимая от остального кода функция. Причём тут инлайн?
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.