Прерывание (interrupt) - это технология, которая позволяет программе реагировать на внешние события. При наступлении события процессор временно прекращает выполнять свою текущую работу и переходит в специальную функцию, называемую обработчиком прерывания. Когда обработка завершается, процессор возвращается к выполнению программы.
Прерывания могут быть как программными, так и аппаратными. На PlayStation программные прерывания часто называются исключениями (exception). Обработку прерываний, по большей части, берет на себя BIOS (хотя и делает это неэффективно - см. на сайте problemkaputt).
Управлением прерываниями занимается сопроцессор COP0. Пользовательский код может взаимодействовать с ним при помощи специальных регистров:
Процесс обработки прерывания выглядит следующим образом:
Перед загрузкой игры BIOS отключает обработку прерываний, поэтому, чтобы работать с периферийными устройствами (геймпад, CD-ROM и пр.), нужно либо вручную установить биты 2 и 10 статусного регистра COP0, либо проинициализировать геймпад функцией BIOS InitPad.
BIOS создает четыре очереди обработчиков прерываний:
Перед вызовом очереди BIOS сохраняет все регистры общего назначения (GPR) в специальный буфер, так что самостоятельно это делать не нужно.
Обработчики прерываний добавляются функцией BIOS SysEnqIntRP и убираются функцией SysDeqIntRP. Обе функции принимают вторым аргументом указатель на следующую структуру:
struct IntRP { struct IntRP* next; // следующий элемент IntRP в связном списке, либо null, если это последний элемент void* func2; // указатель на функцию, которая вызывается второй, если func1 вернула ненулевое значение void* func1; // указатель на функцию, которая вызывается первой int flag; // зарезервированное значение, должно быть равно нулю };
Обработчик вызывается следующим образом:
Чтобы безопасно поместить элемент в очередь, необходимо обернуть этот вызов в критическую секцию, временно отключив аппаратные прерывания. Это предотвратит сбой в работе консоли, который мог бы произойти, если бы во время изменения указателей очереди возникло другое прерывание.
BIOS по цепочке вызывает все зарегистрированные обработчики прерываний. Каждый обработчик должен проверить нужный бит регистра I_STAT и подтвердить его, записав в него ноль. Например:
int irqVBlank() { if ((*I_STAT & IRQ0) && (*I_MASK & IRQ0)) { // Reset VBlank bit *I_STAT = ~IRQ0; } return 0; }
Поскольку такой подход может привести к конфликтам, не рекомендуется навешивать на одну и ту же линию прерываний более одного обработчика.