### **Прерывания (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 (иначе артефакты). **Пример кода:** ```mips 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** позволяет: - Читать данные с диска **без блокировки основного цикла игры**. - Загружать уровни/аудио **в фоне**. **Пример:** ```mips cd_read_done: # IRQ2 вызвано — данные готовы la $t0, cd_buffer sw $t0, 0x1F8010A0 # DMA CD-ROM jr $ra ``` --- ### **(3) Обработка ввода (геймпады)** Прерывание **IRQ5** сообщает: - Когда игрок нажал кнопку (без задержек). - Поддерживает **вибрацию** (если есть DualShock). **Пример:** ```mips 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. Обработчик: - Сохраняет регистры в стек. - Выполняет нужные действия (например, обновляет графику). - Очищает флаг прерывания. - Восстанавливает регистры и возвращает управление. **Пример обработчика на ассемблере:** ```mips 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)** ```mips 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. Обработка прерывания** Типичный обработчик: ```mips 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) ```c 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. **Разрешить все прерывания**: ```c SetIntrMask(0xFFFF); ``` 2. **Разрешить только VBLANK и CD-ROM**: ```c SetIntrMask(0x0005); // 0x0001 (VBLANK) | 0x0004 (CD-ROM) ``` 3. **Запретить все прерывания**: ```c SetIntrMask(0x0000); ``` ## Типичное использование в сочетании с другими функциями ```c // Сохраняем текущую маску 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. Регистры прерываний Основные регистры (все адреса в шестнадцатеричном формате): ```mips li $t0, 0x1F801070 # I_STAT - статус прерываний li $t1, 0x1F801074 # I_MASK - маска прерываний ``` ## 2. Установка обработчика прерываний Вектор прерываний находится по адресу 0x80000080. Пример установки обработчика: ```mips .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 прерывания: ```mips # Разрешаем VBLANK в маске li $t0, 0x1 sw $t0, 0x1F801074 # I_MASK # Включаем прерывания глобально mfc0 $t0, $12 # Читаем Status register ori $t0, $t0, 0x1 # Разрешаем прерывания mtc0 $t0, $12 # Записываем обратно ``` ## 4. Обработка VBLANK Пример обработчика вертикального гашения: ```mips 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. Обработка контроллера Пример обработки ввода с геймпада: ```mips 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. Критические секции Отключение/включение прерываний: ```mips 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: ```mips 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 используются: ```c SetIntrMask(0x400); // Разрешить прерывания EnterCriticalSection(); // Запретить прерывания ExitCriticalSection(); // Разрешить прерывания ``` 3. Стандартная инициализация в PsyQ: ```c ResetCallback(); SetIntrHandler(MyHandler); ``` Эти примеры демонстрируют низкоуровневую работу с прерываниями на ассемблере MIPS R3000, который используется в PlayStation 1. В реальных проектах часто используют комбинацию ассемблера и C для удобства.