Это старая версия документа!
Содержание
Прерывания
Прерывание (interrupt) - это технология, которая позволяет программе реагировать на внешние события. При наступлении события процессор временно прекращает выполнять свою текущую работу и переходит в специальную функцию, называемую обработчиком прерывания. Когда обработка завершается, процессор возвращается к выполнению программы.
Прерывания могут быть как программными, так и аппаратными. На PlayStation программные прерывания часто называются исключениями (exception). Обработку прерываний, по большей части, берет на себя BIOS (хотя и делает это неэффективно - см. на сайте problemkaputt).
Управлением прерываниями занимается сопроцессор COP0. Пользовательский код может взаимодействовать с ним при помощи специальных регистров:
- SR (cop0r12) - статусный регистр; позволяет произвольно включать и выключать обработку прерываний, а также маскировать те или иные линии.
- Cause (cop0r13) - регистр причины прерывания. Предоставляет битовые флаги линий прерываний и код, идентифицирующий тип последнего прерывания. Регистр имеет 8 линий: 2 для исключений (биты 8-9) и 6 для аппаратных прерываний (биты 10-15). PlayStation использует только одну линию аппаратных прерываний (бит 10), все остальные всегда нулевые.
- EPC (cop0r14) - содержит адрес инструкции, которая вызвала исключение. Если произошло аппаратное прерывание, то EPC содержит адрес следующей инструкции, которая должна выполниться после обработки.
- BadVaddr (cop0r8) - используется при исключениях шины. Содержит адрес, обращение по которому вызвало исключение.
Процесс обработки прерывания выглядит следующим образом:
- COP0 сохраняет значение программного счетчика (PC) в регистр EPC. Если текущая инструкция - это слот задержки ветвления, то будет записано значение PC-4, т.е. адрес предыдущей инструкции. В регистре Cause устанавливается бит, означающий, что прерывание сработало в слоте задержки ветвления.
- Код прерывания записывается в регистр Cause.
- Отключается обработка дальнейших прерываний до тех пор, пока не завершится обработка текущего. Включается режим ядра (на PlayStation он обычно и так всегда включен). Предыдущее состояние сохраняется в регистр SR.
- Если сработало исключение шины, проблемный адрес записывается в BadVaddr.
- CPU переходит по вектору 0xBFC00180, если в SR установлен бит Boot Exception Vectors (BEV), либо по вектору 0x80000080 в противном случае. BEV устанавливается при включении, но BIOS его зануляет. По адресу 0x80000080, который находится в рабочем пространстве BIOS, начинается базовый код обработки прерывания.
Перед загрузкой игры BIOS отключает обработку прерываний, поэтому, чтобы работать с периферийными устройствами (геймпад, CD-ROM и пр.), нужно либо вручную установить биты 2 и 10 статусного регистра COP0, либо проинициализировать геймпад функцией BIOS InitPad.
BIOS создает четыре очереди обработчиков прерываний:
- 0 - Системные исключения - syscall, деление на ноль, переполнение памяти;
- 1 - Прерывания от системных таймеров и VBlank;
- 2 - Зарезервировано;
- 3 - Обработчики аппаратных прерываний периферии.
Перед вызовом очереди BIOS сохраняет все регистры общего назначения (GPR) в специальный буфер, так что самостоятельно это делать не нужно.
Установка пользовательских обработчиков
Обработчики прерываний добавляются функцией BIOS SysEnqIntRP и убираются функцией SysDeqIntRP. Обе функции принимают вторым аргументом указатель на следующую структуру:
struct IntRP { struct IntRP* next; // следующий элемент IntRP в связном списке, либо null, если это последний элемент void* func2; // указатель на функцию, которая вызывается второй, если func1 вернула ненулевое значение void* func1; // указатель на функцию, которая вызывается первой int flag; // зарезервированное значение, должно быть равно нулю };
Обработчик вызывается следующим образом:
- func2(func1()), если присутствуют обе функции;
- func1(), если присутствует первая функция, но отсутствует вторая;
- В иных случаях ничего не вызывается.
Чтобы безопасно поместить элемент в очередь, необходимо обернуть этот вызов в критическую секцию, временно отключив аппаратные прерывания. Это предотвратит сбой в работе консоли, который мог бы произойти, если бы во время изменения указателей очереди возникло другое прерывание.
Функции BIOS, относящиеся к обработке прерываний
- С(02h) - SysEnqIntRP(priorityIndex, handler) - добавляет обработчик прерывания в очередь.
- С(03h) - SysDeqIntRP(priorityIndex, handler) - убирает обработчик прерывания из очереди.
- int EnterCriticalSection(), вызов через syscall с номером 0x01 в регистре $a0 - отключает обработку прерываний, зануляя биты 2 и 10 регистра SR (cop0r12). Возвращает 1, если оба бита были установлены, и 0 в противном случае. Эту функцию нужно вызывать в редких случаях, когда нужно выполнить код, который не должен прерываться.
- void ExitCriticalSection(), вызов через syscall с номером 0x01 в регистре $a0 - включает обработку прерываний, устанавливая биты 2 и 10 регистра SR (cop0r12).
