Содержание

Прерывания

Прерывание (interrupt) - это технология, которая позволяет программе реагировать на внешние события. При наступлении события процессор временно прекращает выполнять свою текущую работу и переходит в специальную функцию, называемую обработчиком прерывания. Когда обработка завершается, процессор возвращается к выполнению программы.

Прерывания могут быть как программными, так и аппаратными. На PlayStation программные прерывания часто называются исключениями (exception). Обработку прерываний, по большей части, берет на себя BIOS (хотя и делает это неэффективно - см. на сайте problemkaputt).

Управлением прерываниями занимается сопроцессор COP0. Пользовательский код может взаимодействовать с ним при помощи специальных регистров:

Процесс обработки прерывания выглядит следующим образом:

  1. COP0 сохраняет значение программного счетчика (PC) в регистр EPC. Если текущая инструкция - это слот задержки ветвления, то будет записано значение PC-4, т.е. адрес предыдущей инструкции. В регистре Cause устанавливается бит, означающий, что прерывание сработало в слоте задержки ветвления.
  2. Код прерывания записывается в регистр Cause.
  3. Отключается обработка дальнейших прерываний до тех пор, пока не завершится обработка текущего. Включается режим ядра (на PlayStation он обычно и так всегда включен). Предыдущее состояние сохраняется в регистр SR.
  4. Если сработало исключение шины, проблемный адрес записывается в BadVaddr.
  5. CPU переходит по вектору 0xBFC00180, если в SR установлен бит Boot Exception Vectors (BEV), либо по вектору 0x80000080 в противном случае. BEV устанавливается при включении, но BIOS его зануляет. По адресу 0x80000080, который находится в рабочем пространстве BIOS, начинается базовый код обработки прерывания.

Перед загрузкой игры 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;
}

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

Функции BIOS, относящиеся к обработке прерываний