298 lines
14 KiB
Markdown
298 lines
14 KiB
Markdown
### **Прерывания (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 для управления конкретными типами прерываний. |