-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Исследование механизма прерываний RISC-V в machine-mode.
Для разрешения прерываний от PLIC: Выставить mie.meie = 1; mstatus.mie = 1 и настроить PLIC.
Для прерываний от CLINT.timer: Выставить mie.mtie = 1; mstatus.mie = 1 и настроить CLINT.timer.
Для прерываний от CLINT.msip: Выставить mie.msie = 1; mstatus.mie = 1 и записать CLINT.msip = 1.
Похоже, что глобальное разрешение mstatus.mie нужно только для закрытия при входе (по mtvec) в обработчик и открытия при выходе (по mret) из обработчика.
При срабатывании прерывания (переход в обработчик), mstatus.mie переписывается в mstatus.mpie, после чего mstatus.mie сбрасывается.
mstatus.mpie (вместе с mstatus.mpp) нужен для поддержки вложенных обработчиков. [riscv-priveleged п.3.1.7.]
Разрядам mip соответствуют сигналы прерываний от устройств:
- mip.meip - plic.pending (одно или несколько внешних прерываний)
- mip.msip - clint.msip (software interrupt при clint.msip = 1)
- mip.mtip - clint.таймер (внутренный таймер при clint.mtime >= clint.mtimecmp)
mip зачем-то доступен для записи, то есть mip.meip, mip.msip и mip.mtip можно сбросить/выставить вручную, но после этого mip вернется в правильное состояние - в соответствии с состояниями сигналалов прерываний: plic.pending, clint.msip, clint.таймер.
mip.meip выставляется независимо от разрешений mie.meie и mstatus.mie.
mip.meip выставляется, когда прерывание есть на выходе PLIC, то есть когда в PLIC всё разрешено для срабатывания прерывания и когда прерывание есть в источнике.
(Например plic.pending выставлен, а plic.threshold = max(закрыто), то mip.meip не выставится).
mip.meip сбрасывается после чтения plic.claim.
Чтобы сбросить mip.meip не переходя в обработчик, нужно чтобы mie.meie и/или mstatus.mie были закрыты, затем сбросить plic.pending (настроить plic.priority и plic.threshold, считать и записать plic.claim - PlicClearPending()). При этом mip.meip сбросится сам.
mip.msip выставляется при записи clint.msip = 1, независимо от разрешений mie.msie и mstatus.mie.
mip.msip сбрасывается при очистке clint.msip = 0, независимо от разрешений mie.msie и mstatus.mie.
mip.mtip выставляется когда clint.mtime >= clint.mtimecmp, независимо от mie.mtie.
mip.mtip сбрасывается при записи clint.mtime и clint.mtimecmp таких, что clint.mtime < clint.mtimecmp.
PLIC: Если mstatus.mie = 0 и mie.meie = 1 и mip.meip = 1, то wfi выполняется как nop (нет перехода в обработчик). Во всех остальных случаях wfi встает в ожидание прерывания. [riscv-priveleged п.3.2.3]
Clint.timer: Если mstatus.mie = 0 и mie.mtie = 1 и mip.mtip = 1, то wfi выполняется как nop (нет перехода в обработчик).
Clint.msip: Если mstatus.mie = 0 и mie.msie = 1 и mip.msip = 1, то wfi выполняется как nop (нет перехода в обработчик).
При выполнении wfi, как nop:
- mstatus.mpie не выставляется.
- mcause не формируется.
- PLIC: Остаются mstatus.mie = 0 и mie.meie = 1 и mip.meip = 1.
- Clint.timer: Остаются mstatus.mie = 0 и mie.mtie = 1 и mip.mtip = 1.
- Clint.msip: Остаются mstatus.mie = 0 и mie.msie = 1 и mip.msip = 1.
Выставляется по возникновению прерывания или исключения и не сбрасывается (обновляется при следующем прерывании/исключении).
Старшая 0x8 значит прерывание.
Переход в обработчик (одинаково и на wfi и на крутилке):
- При переходе в обработчик по mtvec, mstatus.mie автоматически сбрасывается и выставляется mstatus.mpie.
- mie, mip не сбраcываются.
- Формируется mcause.
- mtval (раньше назывался mbadaddr) формируется только при exception, содержит дополнительную информацию об exception.
Возврат из обработчика по mret (одинаково и на wfi и на крутилке):
- Инструкция mret выполняет переход по адресу в mepc (pc = mepc).
- mstatus.mie выставляется, а mstatus.mpie остается выставленным. mstatus.mie выставляется в любом случае, даже если внутри обработчика был снят принудительно.
- mie, mip не сбраcываются.
- mcause не сбрасывается.
При возникновении exception, csr mepc содержит pc инструкции, на которой произошел exception.
И возврат из обработчика по mret происходит на ту же инструкцию: pc = mepc (а не к следующей инструкции). И, соответственно, опять возникает exception. [riscv-priveleged.pdf п.3.1.21]
При прерывании на wfi происходит mepc = pc + 4. [riscv-priveleged.pdf п.3.2.3]. То есть возврат из обработчика по mret на следующую после wfi инструкцию.
При возникновении прерывания на произвольной иструкции (не wfi), возврат по mret происходит на ту же инструкцию pc = mepc.
Таким образом, при exception, выход из обработчика надо делать либо через обязательную перенастройку mepc + возврат по mret; либо через longjmp(...).
Адрес, записанный в mtvec и, соответственно, адрес обработчика должны быть выравнены на 32bit.
В соответствии с riscv-plic-spec, у PLIC может быть несколько hart(context). При этом hart-ы PLIC соотвестсвуют hart-ам ядра, то есть если в ядре единственный hart, то и в PLIC - единственный hart(context).
Для разрешения прерываний PLIC: Выставить биты plic.enable; Настроить значения источников plic.priority (plic.priority должны быть больше 0). Настроить значения: plic.priority > plic.threshold (для простоты можно plic.threshold = 0).
Чем больше plic.priority, тем выше приоритет.
Если у источников приоритеты одинаковые и прерывания пришли одновременно, то приоритет определяется номером источника - но тут, чем меньше номер, тем выше приоритет.
plic.priority[...] = 0 - прерывание от данного источника закрыто.
Если plic.priority <= plic.threshold, то прерывание от данного источника закрыто, перехода в обработчик не будет. Если plic.threshold = 0, то прерывания от всех источников с ненулевым plic.priority разрешены.
Но при этом plic.pending, всё равно выставляется. Это вроде нормально, но нигде это не описано (но это не соответствует riscv-plic-specs fig.2 и riscv-priveleged fig.7.1).
Пришло прерывание, когда было настроено plic.priority <= plic.threshold, то есть висит plic.pending (и всё остальное открыто):
Если перенастроить plic.threshold так, чтобы стало plic.priority > plic.threshold (например plic.threshold = 0), то не происходит переход на обработчик.
Но если перенастроить plic.priority так, чтобы стало plic.priority > plic.threshold, то происходит переход на обработчик.
Источников всего 3 шт. (ID = 1, 2, 3). 0й источник не используется.
То есть max значение каждого plic.priority[...] = 3.
max значение plic.threshold = 3.
plic.enable = 0xe - когда все три разрешены.
plic.pending = 0xe - когда все три сработали.
При записи в WARL поля контроль и коррекция производится побитово (а не по значению).
Пример:
SetPlicThreshold(7); //0x3 (WARL)
SetPlicThreshold(6); //0x2 (WARL)
SetPlicThreshold(4); //0x0 (WARL)
SetPlicThreshold(9); //0x1 (WARL)
Для завершения обработки прерывания нужно считать ID из plic.cliam и затем записать считанный ID в plic.cliam.
Выставлен plic.pending:
При чтении plic.cliam, plic.pending для соответстующего источника становится равным 0. Но при этом gateway остается закрытым, то есть новое прерывание на этот же источник не примется.
Gateway соответствующего источника открывается только после записи соответствующего ID в plic.cliam.
Выставлено несколько plic.pending от разных источников:
При чтении plic.claim первый ID будет от самого приоритетного источника.
Порядок завершения не важен: Можно читать plic.claim и сразу записывать plic.claim = ID для каждого ID; Или можно сначала несколько раз прочитать plic.claim для всех ID(пока он не станет равным 0), и затем несколько раз записать plic.claim = ID для всех считанных ID.
Чтобы очистить plic.pending, нужно настроить plic.priority, разрешить plic.enable и затем считать и опросить plic.claim. При этом настройка plic.threshold не даст никакого эффекта.
[E51 п.7.4; п.7.7; п.7.8]
Поле clint.msip доступно для записи. Оно WARL: значение приводится к 0x1.
При записи clint.msip = 1, выставляется mip.msip, независимо от разрешений mie.msie и mstatus.mie.
При очистке clint.msip = 0, сбрасывается mip.msip, независимо от разрешений mie.msie и mstatus.mie.
Чтобы при прерывании произошел переход на обработчик, важно соблюсти порядок действий: Сначала clint.msip = 1; Затем разрешения mie.msie = 1 и mstatus.mie = 1.
Если сделать в обратном порядке, то mip.msip сформируется, а переход на обработчик не произойдёт.
Если в обработчике не очистить clint.msip и/или не закрыть mie.msie, то после возвращения из обработчика, прерывание сохранится и снова улетит на обработчик. (но не сразу, а через небольшое время !?)
Один тик clint.mtime = 100 тиков mcycle.
mip.mtip выставляется, когда clint.mtime >= clint.mtimecmp, независимо от mie.mtie.
Изначально clint.mtime = 0 и clint.mtimecmp = 0, и соответственно, mip.mtip выставлен, и если выставить разрешения mie.mtie и mstatus.mie, то произойдёт переход в обработчик.
Поэтому сначала нужно настроить clint.таймер (сначала clint.mtimecmp, затем clint.mtime); Затем выставить разрешения mie.mtie и mstatus.mie.
Переход в обработчик происходит если выставлен mip.mtip и выставлены разрешения mie.mtie и mstatus.mie.
mip.mtip и само прерывание можно сбросить, записав новое значение в clint.mtimecmp, такое, что clint.mtime < clint.mtimecmp. [riscv-priveleged п.3.1.15]
clint.mtime тоже доступен для записи. Можно записать оба поля - и clint.mtime и clint.mtimecmp, такие, что clint.mtime < clint.mtimecmp.
Например для отсчёта определенного промежутка времени, таймер можно настроить так:
clint.mtimecmp = 1000000, clint.mtime = 0.
или для отсчёта того же промежутка времени:
clint.mtimecmp = clint.mtime + clint.mtimecmp, а clint.mtime не менять.