Files
SergObsidian/PERSONAL PROJECTS/PS1 DOCS/PS1 IRQ.md
2025-04-19 22:12:43 +05:00

497 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

### **Прерывания (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 для удобства.