Обработка прерываний в MIPS-процессорах

Сегодня, дорогие мои, я расскажу вам про обработку исключений в процессорах MIPS-архитектуры. Но вначале - предисловие:

  1. Если вы считаете, что "только идиот будет рассказывать такие элементарные вещи" - что ж, простите, что не угодил вашему притязательному вкусу. Имею надежду, что это хоть как-то кому-то поможет.
  2. Все описанное - из личного опыта. Но из MIPS-архитектур довелось поработать только с процессорами серии "Мультикор". Это российские процы, подробнее - на http://multicore.ru/index.php?id=43 (не реклама!). Вообще-то они DSP, но управляющее ядро у них как раз MIPS32. К чему это я? Ах да, к тому, что все рассказанное ниже было испробовано только с этими процами и возможно, при работе с другими MIPS-совместимыми архитектурами надо будет что-то менять.
  3. Могу иногда быть некорректен, например в терминологии. В этом случае - с удовольствием принимаю поправки и предложения.

Итак, "от печки". В архитектуре MIPS32 реализован механизм исключений. По факту возникновения исключения процессор делает следующее:

  1. Переходит в режим обработки исключения выставлением бита в регистре Status сопроцессора CP0. При этом прерывания запрещаются.
  2. Совершается переход на адрес обработчика исключения. В отличие от других архитектур, у MIPS нет таблицы векторов прерываний. То есть, в зависимости от конкретного исключения адрес перехода может варьироваться, но так или иначе это конечное число адресов, причем небольшое. Пример: в процессорах "мультикор" обработчик прерывания от таймера должен находиться по адресу 0xBFC0_0380 или 0x8000_0180. Какой именно это адрес - определяется битом в регистре Status сопроцессора CP0. Переназначить адрес обработчика на какой-нибудь 0x1234_5678 - нельзя. Адрес, на котором мы находились перед исключением, сохраняется в регистр EPC сопроцессора CP0.
  3. Процессор исполняет инструкции, расположенные по адресу обработчика. Долго исполняет. До тех пор, пока ему не встретится команда ERET. По исполнению этой команды процессор переходит в обычный режим (из режима обработки исключения) и возвращается на адрес, сохраненный в EPC.

Это общий алгоритм. Более подробно уже надо смотреть в даташите на конкретный процессор.

Теперь немного о классификации исключений. Это может быть исключение по ошибке адресации - когда мы обращаемся в тот адрес, в который по каким-то причинам обращаться нельзя. Это может быть исключение по "незнакомой команде" - когда процу попадается незнакомый опкод - например при неправильном переходе, когда проц попадает не на программу и на данные. Исключение по точке останова, и прочее, и прочее. Среди всех этих типов стоит выделить так называемое исключение прерывания. Это то, что в остальных процессорах называется просто прерыванием.

Исключение прерывания - это то исключение, которое возникает при возникновении прерывания от любого устройства процессора - будь это таймер, порт ввода-вывода или, например, DSP-ядро. То есть, еще раз: неважно, от чего прерывание - от таймера, от UART, от другого порта, от DMA, или же это внешнее прерывание - переход будет на один и тот же адрес. И определять, от какого устройства это прерывание возникло - надо программно. Это отличается от привычной (для меня) по AVR и x86 системы, но в этом есть своя прелесть.

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

  1. Написать обработчики.
  2. Разместить обработчики по векторам используемых исключений.
  3. Разрешить прерывания.

Дисклаймер: дальше речь пойдет только про обработку именно исключения прерывания. Обработка других исключений, в общем-то, слабо отличается.

Что должен делать обработчик.

  1. Определить "причину" исключения.
  2. Сделать непосредственно обработку - это уже зависит сугубо от целевой задачи.
  3. "Устранить" причину исключения и выполнить ERET.

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

Итак, определение "причины". В первую очередь, надо анализировать регистр Cause сопроцессора CP0. В нем есть поле, которое называется ExcCode. Если оно равно 0 - это исключение прерывания. Для остальных типов исключений - свое значение.

Далее нам необходимо понять, откуда это прерывание - от внутренних устройств проца, или же внешнее? Для этого дальше читаем регистры Cause и Status. Поля в них называются IP[7:0] и IM[7:0] соответственно. Поле IP - это запросы прерывания, а IM - маска. Биты IP[1:0] - это программные прерывания. Они выставляются записью непосредственно в это поле. IP[5:2] - это биты, соответствующие прерываниям nIRQ[3:0]. И бит IP[7] - это бит, объединяющий прерывания от всех внутренних устройств по ИЛИ - то есть, этот бит однозначно сигнализирует о прерывании от накристалльного устройства. Разумеется, при условии, что это прерывание разрешено в поле IM регистра Status.

В случае, если это программное или внешнее прерывание - "причина" однозначно установлена и можно приступать непосредственно к обработке. Если же это прерывание от внутреннего устройства - необходимо анализировать регистры QSTR и MASKR. В первом, согласно названию, отображаются запросы прерывания, а во втором - их маски. Соответственно, если какой-то бит в обоих регистрах выставлен в единицу - значит, прерывание от соответствующего устройства разрешено и активно.

После этого, опять же, можно приступать к обработке прерывания.