Ровно два регистра - mtime и mtimecmp.
К1921ВГ015 общее
Модераторы: ea, dav, bkolbov, Alis, pip, _sva_
-
- Сообщения: 17
- Зарегистрирован: 10 июн 2025, 12:11
- Предприятие: HomeWork
Re: К1921ВГ015 общее
Был несколько озадачен, что если в примере "mtimer" включить TMR32 одновременно с mtimer, то при запуске оно зависает наглухо. Однако если mtimer не запускать (закомментировать вызов mtimer_init() в main() ), то всё работает.
Дело оказалось в следующем.
Когда mtimer не инициализируется, вызов TMR32_IRQHandler() происходит по следующему пути (call stack):
При этом в обработчике trap_handler() происходит безусловный вызов PLIC_MachHandler(), в котором уже проверяется, назначен ли соответствующий прерыванию обработчик, и если да, то вызывается
Если же инициализировать mtimer, то в функции mtimer_init() через вызов riscv_irq_set_handler() в массив riscv_handler_map[] под седьмым номером записывается адрес функции MTIMER_IRQHandler(), а затем вызывается riscv_irq_init(), где переопределяется глобальный обработчик прерываний на irq_entry(). В результате стек вызовов к обработчику прерывания от mtimer имеет вид:
Когда происходит прерывание от TMR32 ("машинное" в терминологии RISC-V), то по соответствующему ему индексу 11 в массиве riscv_handler_map[] оказываются нули, и программа попадает в бесконечный цикл в irq_entry().
Получается, в зависимости от того, какой таймер используется, применяются разные подходы к обработке прерываний. В связи с этим у меня вопрос: а как правильно то?
Дело оказалось в следующем.
Когда mtimer не инициализируется, вызов TMR32_IRQHandler() происходит по следующему пути (call stack):
Код: Выделить всё
TMR32_IRQHandler@0x400002f0 (main.c:140)
PLIC_MachHandler@0x4000049a (platform/source/plic.c:165)
trap_handler@0x40000ba2 (platform/source/plic.c:219)
trap_entry@0x40000200 (platform/source/startup_k1921vg015.S:262)
Если же инициализировать mtimer, то в функции mtimer_init() через вызов riscv_irq_set_handler() в массив riscv_handler_map[] под седьмым номером записывается адрес функции MTIMER_IRQHandler(), а затем вызывается riscv_irq_init(), где переопределяется глобальный обработчик прерываний на irq_entry(). В результате стек вызовов к обработчику прерывания от mtimer имеет вид:
Код: Выделить всё
MTIMER_IRQHandler@0x400003c0 (main.c:147)
irq_entry@0x40000940 (platform/source/riscv-irq.c:84)
Получается, в зависимости от того, какой таймер используется, применяются разные подходы к обработке прерываний. В связи с этим у меня вопрос: а как правильно то?
-
- Сообщения: 17
- Зарегистрирован: 10 июн 2025, 12:11
- Предприятие: HomeWork
Re: К1921ВГ015 общее
Сморите, системный таймер - это часть ядра и у него есть отдельный признак, что прерывание именно от сиситемного таймера. Для всего остального (TMR32, DMA и прочее) - признак ровно один - "внешнее прерывание", и обрабоатывать нужно отдельно, используя PLIC.
А то, что в примере "mtimer" ошибка - так то бывает

Вот проверенный (во всяком случае у меня) пример https://gitflic.ru/project/rabidrabbit/ ... nch=master с 16-битным таймером TMR0 (по прерыванию от него мигает светодиод) и системным таймером, по прерыванию от которого который обновляется счётчик миллисекунд, по которому устроена задержка между выводом строк в UART0.
Re: К1921ВГ015 общее
Да, я уже понял, что при прерывании всегда осуществляется переход на один определённый адрес, и там уже обработчик должен разбираться с тем, что произошло. Но сейчас я не об этом.RabidRabbit писал(а): ↑22 июн 2025, 12:57Сморите, системный таймер - это часть ядра и у него есть отдельный признак, что прерывание именно от сиситемного таймера. Для всего остального (TMR32, DMA и прочее) - признак ровно один - "внешнее прерывание", и обрабоатывать нужно отдельно, используя PLIC.
Если в примере "mtimer" закомментировать инициализацию mtimer-а, при возникновении прерывания управление передаётся обработчику в ассемблерном файле. Там происходит сохранение контекста (макрос context_save) и регистра mstatus, а затем вызов обработчика следующего уровня, где уже проверяется, прерывание это от ядра или от периферии. И т. д. После завершения прерывания происходит восстановление контекста и mstatus.
Однако если включить инициализацию mtimer, вместо обработчика в ассемблерном файле подставляется обработчик в riscv-irq.c. Как я понял, сохранение контекста и mstatus там не выполняется. Во всяком случае явно.
Мой вопрос заключается как раз в том, насколько такая замена обработчика допустима. Не случится ли однажды неожиданное поведение программы, если будет вызываться обработчик, в котором контекст и mstatus не сохраняются?
-
- Сообщения: 17
- Зарегистрирован: 10 июн 2025, 12:11
- Предприятие: HomeWork
Re: К1921ВГ015 общее
В примере mtimer явная ошибка, так как там для обработки прерывания подставляется адрес функции void MTIMER_IRQHandler(), которая не объявлена, как обработчик прерывания. Если сделать так "__attribute__((interrupt)) void MTIMER_IRQHandler()" и добавить в файле main.c примера mtimer после строки
riscv_irq_set_handler(RISCV_IRQ_MTI, MTIMER_IRQHandler);
строку
riscv_irq_set_handler(RISCV_IRQ_MEI, PLIC_MachHandler);
теоретически, должно заработать и одновременно. Т.е. этот пример (в том виде, в каком он предоставляется) не предназначен для одновременного использования прерываний от TMR32 и от системного таймера, а видимо получен "копипастой" примера от TMR32.
Т.е. по норме обработчик должен быть ровно один

-
- Сообщения: 17
- Зарегистрирован: 10 июн 2025, 12:11
- Предприятие: HomeWork
Re: К1921ВГ015 общее
Поспешил. Не так "__attribute__((interrupt)) void MTIMER_IRQHandler()" а так "__attribute__((interrupt)) void irq_entry (void)" (в файле riscv-irq.c)RabidRabbit писал(а): ↑22 июн 2025, 16:39 В примере mtimer явная ошибка, так как там для обработки прерывания подставляется адрес функции void MTIMER_IRQHandler(), которая не объявлена, как обработчик прерывания. Если сделать так "__attribute__((interrupt)) void MTIMER_IRQHandler()"
Re: К1921ВГ015 общее
Посмотрел, походил отладчиком.RabidRabbit писал(а): ↑22 июн 2025, 16:39Опять же можете помотреть пример по ссылке, которую я закидывал в предыдущем ответе.
У вас тоже прерывание направляется на сишную функцию trap_handler(). Данная функция реализована примерно так, как я себе и представлял: проверяем тип прерывания (таймер, периферия, софтовое) и вызываем соответствующий обработчик. И у вас также не выполняется сохранение контекста и mstatus, как и в примере НИИЭТ "mtimer" при выборе таймера mtimer. Но которое в НИИЭТовском примере выполняется при выборе таймера TMR32.
В общем, мой вопрос о том, насколько необходим этот код, расположенный в "startup_k1921vg015.S" по метке "trap_entry", всё ещё актуален.
Кстати RabidRabbit, откуда взяли исходники, которые в каталоге "libbaremetal"? В НИИЭТовских примерах я их не встречал.
-
- Сообщения: 17
- Зарегистрирован: 10 июн 2025, 12:11
- Предприятие: HomeWork
Re: К1921ВГ015 общее
Задайте себе вопрос, зачем нужно сохранять mstatus?Vcoder писал(а): ↑22 июн 2025, 19:00 Посмотрел, походил отладчиком.
У вас тоже прерывание направляется на сишную функцию trap_handler(). Данная функция реализована примерно так, как я себе и представлял: проверяем тип прерывания (таймер, периферия, софтовое) и вызываем соответствующий обработчик. И у вас также не выполняется сохранение контекста и mstatus, как и в примере НИИЭТ "mtimer" при выборе таймера mtimer. Но которое в НИИЭТовском примере выполняется при выборе таймера TMR32.

Далее - что Вы понимаете под "сохранением контекста"? В данном случае при входе в обработчик прерывания нужно сохранить на стеке содержимое тех регистров, которые будет использовать код самого обработчика прерывания, чтобы перез выходом загрузить эти значения обратно и вернуться в точку, откуда продолжится прерванное выполнение так сказать "без следов", не нарушив дальнейшее исполнение прерванного кода. И такое сохранение регистров обеспечивает атрибут interrupt, указанный для функции trap_handler(), кроме этого, этот атрибут обеспечивает выход из функции trap_handler() командой mret, вместо команды ret.
На мой взгляд так - если Вы будете использовать обработчик прерываний из startup_k1921vg015.S - то и тот код нужно оставить.
"Потырено" понемногу отовсюду, включая musl libc, picojpeg, исходники ардуиновских библиотек и прочее в github. Что-то подсмотрено в НИИЭТовских исходниках, ибо они хоть как-то дополняют довольно скупую на подробности документацию.
Re: К1921ВГ015 общее
Это было первое, что я сделал.

Ровно то, что делает макрос "context_save" в файле memasm.h (строка 104). Опять же, моих знаний в ассемблере недостаточно, чтобы точно понять происходящее, но в целом похоже что да - сохранение регистров.
-
- Сообщения: 17
- Зарегистрирован: 10 июн 2025, 12:11
- Предприятие: HomeWork
Re: К1921ВГ015 общее
Это дело наживное

riscv64-unknown-elf-objdump -dS builded.elf | less
При -O0 компилятор буквально следует правилам языка, и по командам достаточно легко разобраться.