Files
2025-04-19 22:12:43 +05:00

19 KiB
Raw Permalink Blame History

Прерывания (Interrupts) на PlayStation 1: Зачем они нужны и как работают?

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


1. Основные типы прерываний

В PS1 есть несколько аппаратных прерываний, каждое обслуживает свою задачу:

Прерывание Источник Задача
IRQ0 VBlank (вертикальная синхронизация) Оповещает о завершении рендеринга кадра. Используется для обновления графики.
IRQ1 GPU Генерируется при ошибках GPU (например, переполнение буфера команд).
IRQ2 CD-ROM Сигнализирует о завершении чтения данных с диска.
IRQ3 DMA Уведомляет о завершении передачи данных (например, в VRAM).
IRQ4 Timer (таймер) Используется для точного измерения времени.
IRQ5 Контроллеры (геймпады) Обработка ввода игрока.
IRQ6 SPU (звук) Прерывание от звукового процессора (например, окончание воспроизведения сэмпла).

2. Зачем они нужны?

(1) Синхронизация графики (VBlank)

Без прерывания IRQ0 (VBlank) игра не могла бы:

  • Плавно обновлять кадры (без разрывов изображения).
  • Безопасно изменять VRAM (иначе артефакты).

Пример кода:

wait_vblank:
    li $t0, 0x1F801070  # I_STAT (регистр статуса прерываний)
    lw $t1, 0($t0)
    andi $t1, 0x1       # Проверяем бит IRQ0 (VBlank)
    beqz $t1, wait_vblank
    nop
    jr $ra

(2) Асинхронная работа с CD-ROM

Прерывание IRQ2 позволяет:

  • Читать данные с диска без блокировки основного цикла игры.
  • Загружать уровни/аудио в фоне.

Пример:

cd_read_done:
    # IRQ2 вызвано — данные готовы
    la $t0, cd_buffer
    sw $t0, 0x1F8010A0  # DMA CD-ROM
    jr $ra

(3) Обработка ввода (геймпады)

Прерывание IRQ5 сообщает:

  • Когда игрок нажал кнопку (без задержек).
  • Поддерживает вибрацию (если есть DualShock).

Пример:

read_pad:
    li $t0, 0x1F801040  # SIO_DATA
    lw $t1, 0($t0)      # Чтение кнопок
    andi $t1, 0xFF      // Фильтруем биты
    jr $ra

3. Как работают прерывания?

  1. Аппаратное событие (например, завершение VBlank) устанавливает флаг в I_STAT (0x1F801070).
  2. Процессор приостанавливает текущий код и переходит к обработчику прерывания (адрес задаётся в $k0).
  3. Обработчик:
    • Сохраняет регистры в стек.
    • Выполняет нужные действия (например, обновляет графику).
    • Очищает флаг прерывания.
    • Восстанавливает регистры и возвращает управление.

Пример обработчика на ассемблере:

irq_handler:
    addiu $sp, -32      # Резервируем стек
    sw $ra, 0($sp)      # Сохраняем $ra
    sw $a0, 4($sp)      # Сохраняем аргументы

    # Проверяем источник прерывания
    li $t0, 0x1F801070
    lw $t1, 0($t0)
    andi $t2, $t1, 0x1  # IRQ0 (VBlank)?
    bnez $t2, handle_vblank
    nop

    # Выход
    lw $a0, 4($sp)
    lw $ra, 0($sp)
    addiu $sp, 32
    eret                # Возврат из прерывания

handle_vblank:
    # Обновляем кадр
    jal update_graphics
    nop
    j irq_handler       # Возврат

4. Почему без прерываний — плохо?

  • Графика: Без VBlank появится tearing (разрывы кадров).
  • Звук: SPU будет работать с задержками.
  • Ввод: Кнопки станут «лагающими».
  • CD-ROM: Игра зависнет на время загрузки.

5. Итог

Прерывания в PS1 — это «нервная система» консоли, которая:

  1. Синхронизирует графику, звук, ввод и загрузку данных.
  2. Избегает busy-waiting (постоянного опроса), экономя ресурсы CPU.
  3. Гарантирует точность (например, 60 FPS через VBlank).

Если ты пишешь на ассемблере, прерывания придётся обрабатывать вручную. В C этим частично занимается PsyQ SDK.

P.S. В твоём переводе Persona 2 прерывания уже работают — иначе игра бы просто не запустилась! 😉 Но если хочешь добавить свои обработчики (например, для кастомной загрузки), дай знать — помогу с кодом.


Как работают прерывания на PlayStation 1: детальный разбор

Прерывания (IRQ) в PS1 — это механизм, позволяющий процессору R3000A мгновенно реагировать на аппаратные события. Вот их полный цикл работы:

1. Инициализация прерываний

Перед использованием нужно:

  • Задать адрес обработчика в $k0 (регистр ядра)
  • Разрешить нужные IRQ в регистре I_MASK (0x1F801074)
setup_irq:
    la $k0, irq_handler  # Устанавливаем обработчик
    li $t0, 0x1F801074
    li $t1, 0x0001       # Разрешаем только IRQ0 (VBlank)
    sw $t1, 0($t0)
    jr $ra

2. Аппаратный триггер

Когда происходит событие (например, VBlank):

  1. CD-ROM контроллер/GPU устанавливает бит в I_STAT (0x1F801070)
  2. Процессор проверяет:
    • Разрешено ли это прерывание в I_MASK
    • Не находится ли он уже в обработчике (бит IEc в регистре SR)

3. Переход в обработчик

Если условия выполнены:

  1. Процессор:
    • Сохраняет PC и SR в специальных регистрах (EPC, ErrorEPC)
    • Переходит в режим ядра (бит KUc = 0)
    • Прыгает по адресу в $k0

4. Обработка прерывания

Типичный обработчик:

irq_handler:
    # 1. Сохраняем контекст
    addiu $sp, -120     # Резервируем место для 30 регистров
    sw $at, 0($sp)
    sw $v0, 4($sp)
    # ... сохраняем все используемые регистры

    # 2. Определяем источник
    li $t0, 0x1F801070
    lw $t1, 0($t0)      # Читаем I_STAT

    # 3. Обработка VBlank
    andi $t2, $t1, 0x1
    beqz $t2, not_vblank
    nop
    jal handle_vblank
    nop

not_vblank:
    # 4. Сбрасываем флаг прерывания
    li $t3, 0xFFFF00FF  # Маска для очистки
    and $t1, $t1, $t3
    sw $t1, 0($t0)      # Пишем обратно в I_STAT

    # 5. Восстанавливаем контекст
    lw $at, 0($sp)
    lw $v0, 4($sp)
    # ...
    addiu $sp, 120

    # 6. Возврат
    eret                # Возврат с восстановлением SR и PC

5. Возврат из прерывания

Инструкция eret:

  • Восстанавливает PC из EPC
  • Восстанавливает SR (включая бит IEc)
  • Возвращает процессор в пользовательский режим

6. Особенности PS1

  1. Приоритеты: Нет аппаратного приоритета - порядок обработки определяется программно
  2. Вложенность: Новые прерывания блокируются, пока IEc = 0
  3. Задержки: Минимальная задержка между прерываниями - 5 тактов

7. Реальный пример из PsyQ

Обработчик VBlank в библиотеке libgs:

  1. Сохраняет матрицу преобразования
  2. Обновляет буферы GPU
  3. Вызывает пользовательский callback

8. Оптимизации

  • Короткие обработчики: Не более 100 тактов
  • Минимальное сохранение: Только используемые регистры
  • Аппаратное ускорение: Для DMA используйте D_ICR (0x1F8010F4)

Для отладки в no$psx используйте точки останова на записи в I_STAT!


Команда SetIntrMask в PS1 (PlayStation 1)

SetIntrMask — это низкоуровневая функция в SDK PlayStation 1 (как в официальном Sony SDK, так и в PsyQ), которая управляет маской прерываний процессора R3000.

Основное назначение

Функция SetIntrMask устанавливает битовую маску, определяющую, какие прерывания будут обрабатываться процессором. Она позволяет включать/выключать определенные типы прерываний.

Синтаксис (в PsyQ)

void SetIntrMask(unsigned int mask);

Битовая маска прерываний

Маска прерываний обычно определяется следующими флагами (конкретные значения могут отличаться в разных версиях SDK):

0x0001 - VBLANK (вертикальное гашение)
0x0002 - GPU
0x0004 - CD-ROM
0x0008 - DMA
0x0010 - TIMER0 (таймер 0)
0x0020 - TIMER1 (таймер 1)
0x0040 - TIMER2 (таймер 2)
0x0080 - Периферийные устройства (контроллеры, память карт)
0x0400 - SPU (звуковой процессор)

Примеры использования

  1. Разрешить все прерывания:

    SetIntrMask(0xFFFF);
    
  2. Разрешить только VBLANK и CD-ROM:

    SetIntrMask(0x0005); // 0x0001 (VBLANK) | 0x0004 (CD-ROM)
    
  3. Запретить все прерывания:

    SetIntrMask(0x0000);
    

Типичное использование в сочетании с другими функциями

// Сохраняем текущую маску
unsigned int old_mask = GetIntrMask();

// Запрещаем все прерывания
SetIntrMask(0x0000);

// Выполняем критический код, который не должен прерываться

// Восстанавливаем предыдущую маску
SetIntrMask(old_mask);

Важные замечания

  1. Альтернативы: В PsyQ часто используют EnterCriticalSection() и ExitCriticalSection(), которые internally работают с SetIntrMask.

  2. Влияние на производительность: Неправильное управление маской прерываний может привести к пропуску важных событий (например, VBLANK).

  3. GPU синхронизация: При работе с GPU часто требуется временно отключать прерывания для атомарных операций.

  4. Сочетание с обработчиками: Даже если прерывание разрешено маской, для него должен быть установлен обработчик через соответствующие функции PsyQ.

Эта функция предоставляет низкоуровневый контроль над прерываниями и обычно используется в сочетании с более высокоуровневыми API PsyQ для управления конкретными типами прерываний.


Вот подробное объяснение работы прерываний в PlayStation 1 с примерами на ассемблере MIPS R3000, который используется в PS1:

1. Регистры прерываний

Основные регистры (все адреса в шестнадцатеричном формате):

li $t0, 0x1F801070  # I_STAT - статус прерываний
li $t1, 0x1F801074  # I_MASK - маска прерываний

2. Установка обработчика прерываний

Вектор прерываний находится по адресу 0x80000080. Пример установки обработчика:

.org 0x80000080  # Вектор прерываний
  j my_irq_handler  # Переход к обработчику
  nop

my_irq_handler:
  # Сохраняем регистры
  addiu $sp, $sp, -32
  sw $ra, 0($sp)
  sw $a0, 4($sp)
  sw $v0, 8($sp)
  
  # Проверяем тип прерывания
  lw $t0, 0x1F801070  # Читаем I_STAT
  lw $t1, 0x1F801074  # Читаем I_MASK
  
  and $t2, $t0, $t1   # Активные и разрешенные прерывания
  
  # Обработка VBLANK
  andi $t3, $t2, 0x1
  beqz $t3, not_vblank
  nop
  
  # Код обработки VBLANK
  jal handle_vblank
  nop
  
not_vblank:
  # Обработка других прерываний...
  
  # Сброс флагов прерываний
  sw $t0, 0x1F801070  # Записываем обратно в I_STAT
  
  # Восстанавливаем регистры
  lw $ra, 0($sp)
  lw $a0, 4($sp)
  lw $v0, 8($sp)
  addiu $sp, $sp, 32
  
  # Возврат из прерывания
  eret

3. Включение прерываний

Пример настройки VBLANK прерывания:

# Разрешаем VBLANK в маске
li $t0, 0x1
sw $t0, 0x1F801074  # I_MASK

# Включаем прерывания глобально
mfc0 $t0, $12      # Читаем Status register
ori $t0, $t0, 0x1   # Разрешаем прерывания
mtc0 $t0, $12      # Записываем обратно

4. Обработка VBLANK

Пример обработчика вертикального гашения:

handle_vblank:
  addiu $sp, $sp, -16
  sw $ra, 0($sp)
  
  # Инкрементируем счетчик кадров
  la $t0, frame_counter
  lw $t1, 0($t0)
  addiu $t1, $t1, 1
  sw $t1, 0($t0)
  
  # Вызов C-функции
  jal update_graphics
  nop
  
  lw $ra, 0($sp)
  addiu $sp, $sp, 16
  jr $ra
  nop

5. Обработка контроллера

Пример обработки ввода с геймпада:

handle_controller:
  addiu $sp, $sp, -16
  sw $ra, 0($sp)
  
  # Чтение состояния контроллера
  li $t0, 0x1F801040  # JOY_DATA
  lw $t1, 0($t0)
  
  # Проверка кнопки START
  andi $t2, $t1, 0x1000
  beqz $t2, no_start
  nop
  
  # Обработка нажатия START
  jal pause_game
  nop
  
no_start:
  lw $ra, 0($sp)
  addiu $sp, $sp, 16
  jr $ra
  nop

6. Критические секции

Отключение/включение прерываний:

disable_interrupts:
  mfc0 $t0, $12
  li $t1, 0xFFFFFFFE
  and $t0, $t0, $t1
  mtc0 $t0, $12
  jr $ra
  nop

enable_interrupts:
  mfc0 $t0, $12
  ori $t0, $t0, 0x1
  mtc0 $t0, $12
  jr $ra
  nop

7. Таймеры

Настройка таймера 0:

setup_timer0:
  # Устанавливаем период (1/60 секунды для NTSC)
  li $t0, 0x1F801100
  li $t1, 0x0000
  sw $t1, 0($t0)       # TIMER0_COUNTER
  li $t1, 0x03FF
  sw $t1, 4($t0)       # TIMER0_MODE
  li $t1, 0x0100
  sw $t1, 8($t0)       # TIMER0_TARGET
  
  # Разрешаем прерывание таймера
  li $t0, 0x1F801074
  lw $t1, 0($t0)
  ori $t1, $t1, 0x10   # BIT 4 - TIMER0
  sw $t1, 0($t0)
  
  jr $ra
  nop

Важные замечания:

  1. Все обработчики должны:

    • Сохранять/восстанавливать регистры
    • Быть максимально быстрыми
    • Сбрасывать флаги прерываний
  2. Для работы с прерываниями в C через PsyQ SDK используются:

    SetIntrMask(0x400);  // Разрешить прерывания
    EnterCriticalSection();  // Запретить прерывания
    ExitCriticalSection();   // Разрешить прерывания
    
  3. Стандартная инициализация в PsyQ:

    ResetCallback();
    SetIntrHandler(MyHandler);
    

Эти примеры демонстрируют низкоуровневую работу с прерываниями на ассемблере MIPS R3000, который используется в PlayStation 1. В реальных проектах часто используют комбинацию ассемблера и C для удобства.