Каков предполагаемый / правильный способ обработки прерываний и использования инструкции WFI risc-v cpu?

Aug 19 2020

Я новичок в программировании на «голом железе» и никогда раньше не сталкивался с прерываниями, но я учился на плате разработчика с питанием от RISC-V FE310-G002 SOC.

Я читал про инструкцию RISC-V WFI (ждать прерывания) и из руководств не похоже, что вы можете положиться на него, чтобы фактически спать ядро. Вместо этого он только предполагает, что выполнение может быть остановлено в системе и что инструкция должна обрабатываться больше как NOP. Однако мне это кажется бесполезным. Рассмотрим следующий фрагмент программы ASM:

wfi_loop:
WFI
J wfi_loop

Это необходимо сделать, поскольку на WFI нельзя полагаться. Однако после MRET обработчика прерывания вы все равно окажетесь в петле. Таким образом, вам придется сделать это условным для глобальной переменной, значение которой обновляется в обработчике прерывания. Это кажется очень запутанным.

Кроме того, если ваша реализация действительно соблюдает инструкцию WFI и прерывание запускается непосредственно перед выполнением инструкции WFI, все ядро ​​остановится, пока не будет запущено какое-либо другое прерывание, поскольку оно вернется до инструкции WFI.

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

Я продолжаю вертеться с этим в голове и, кажется, не могу придумать безопасного использования. Возможно, если вы атомарно включите прерывания в CSRRS, а затем сразу вызовете WFI следующим образом:

CSRRSI zero, mie, 0x80
wfi_loop:
WFI
J wfi_loop
NOP
NOP

Затем не забудьте увеличить регистр mepc на 8 байтов перед вызовом MRET из обработчика прерывания. Перед возвратом прерывание также необходимо будет снова отключить в регистре mie внутри обработчика прерывания. Это решение будет безопасным только в том случае, если все WFI, J и NOP закодированы как 4-байтовые инструкции, независимо от того, используются ли сжатые инструкции. Это также зависит от того, достигнет ли программный счетчик инструкции WFI до того, как станет возможным запуск прерывания после того, как оно будет разрешено инструкцией CSRRSI. Это позволит запустить прерывание в безопасном месте в коде и вернуть его таким образом, чтобы оно вырвалось из цикла, который его ждал.

Думаю, я просто пытаюсь понять, какое поведение я могу ожидать от оборудования и, следовательно, как правильно вызывать и возвращаться из прерываний и использовать инструкцию WFI?

Ответы

3 ErikEidt Aug 19 2020 at 18:22

Должна быть одна задача / поток / процесс, предназначенный для холостого хода, и он должен выглядеть как ваш первый бит кода.

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

Когда происходит прерывание, которое разблокирует какой-то другой поток, подпрограмма обслуживания прерывания должна возобновить этот заблокированный поток вместо прерванного простаивающего потока.

Обратите внимание, что поток, блокирующий ввод-вывод, также прерывается - он прерывается из-за собственного использования ecall. Это исключение является запросом на ввод-вывод и вызывает блокировку этого потока - его нельзя возобновить, пока запрос ввода-вывода не будет удовлетворен.

Таким образом, поток, который заблокирован на вводе-выводе, приостанавливается так же, как если бы он был прерван - а прерывание по часам или прерывание ввода-вывода способно возобновить другой процесс, чем тот, который был немедленно прерван, что произойдет в случае, если незанятый процесс был запущен, и произошло какое-то событие, которого ожидал процесс.


Я использую scratchcsr, чтобы указать на блок контекста для текущего процесса / потока. При прерывании я сохраняю наименьшее количество регистров, необходимых для (начала) обслуживания прерывания. Если прерывание приводит к тому, что какой-то другой процесс / поток становится работоспособным, то при возобновлении работы из прерывания я проверяю приоритеты процесса и могу выбрать переключение контекста вместо возобновления того, что было прервано. Если я возобновлю то, что было прервано, это быстрое восстановление. И чтобы переключить контексты, я заканчиваю сохранение контекста ЦП прерванного потока, затем возобновляю другой процесс / поток, переключая scratchрегистр.

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

4 MargaretBloom Aug 19 2020 at 18:22

Таким образом, вам придется сделать это условным для глобальной переменной, значение которой обновляется в обработчике прерывания.

Вы должны сделать это независимо от реализации, так wfiкак вы не знаете, какое событие вызвало пробуждение оленя.
У вас может быть включено n прерываний при выполнении, wfiи любое из них могло быть вызвано.

wfiэто оптимизация , она экономит электроэнергию, пока что-то не произойдет. Как вы отметили, планировщик ОС может оказаться в состоянии, когда ни один поток не является планируемым (например, все они ждут ввода-вывода или просто его нет), в этом случае он должен делать что-то вроде (со всей необходимой видимостью и семантикой атомарности):

while ( ! is_there_a_schedulable_thread());

Это просто ожидание .
Но вместо того, чтобы закручивать замкнутый цикл (что может снизить производительность и мощность), планировщик может использовать:

while ( ! is_there_a_schedulable_thread())
{
  __wfi();
}

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

Даже в случае отсутствия потоков, пробуждение каждые x микросекунд (из-за прерывания таймера) лучше, чем тратить впустую цикл питания.

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

Однако вы не можете использовать его wfiвезде, или вы можете обнаружить, что ожидаете прерывания, которого никогда не бывает (на самом деле, это привилегированная инструкция).

Думайте об этом как об оптимизации для координации с оборудованием.

В частности, он не был разработан как способ гарантировать, что прерывание было запущено:

void wait_for_int(int int_num)
{
   //Leave only interrupt int_num enabled
   enable_only_int(int_num);
   __wfi();
   restore_interrupts();
}

Это можно было бы использовать таким образом, учитывая конкретную реализацию RISC-V, но, как вы видите из псевдокода, это не совсем удобно.
Отключение всех прерываний, кроме одного, обычно не может позволить себе ОС.
А вот встроенное приложение могло.