Files
Sprinter-SDCC/tests/w0page/w0page.c
T
snark13 c4a512200f tests/w0page + docs §9в: атласы в страницах W0 — probe-тест (все PASS)
Схема: атлас в EMM-странице, на время sprite_update страница
подключается в W0.  Все прерывания исполняют 0x38 текущей страницы W0
(IM1 напрямую, IM2-трамплин чейнит jp 0x0038) — защита: стаб в первых
0x100 байтах страницы (0x38: JP на W2-хелпер: свап на страницу DSS →
честный 0x38 → restore спрайт-страницы; 0x66: RETN).

Проверено в dev-MAME (tests/w0page, все PASS):
  P1 ESTEX READ в W3-замапленную страницу;
  P3 IM1: ~5 c busy-цикла с EI при странице в W0 — жив, сентинел цел;
  P4 IM2: 100 кадровых прерываний посчитаны при подключенной странице
     (тракт трамплин→чейн→стаб→DSS→restore);
  P5 accel-блит src=0x0100 (W0) — пиксели верны по ОЗУ-копии.

Формат .atl в §3.1 дополнен: атлас ≤ 16К−0x100 (одна страница, один
файл), вариант II — 0x100-заголовок, файл-офсет == офсет страницы ==
W0-адрес, загрузка = read() целиком + патч стаба.  На железе
перепроверить.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 12:30:24 +03:00

244 lines
9.7 KiB
C
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.
/*
* w0page — проверки схемы «спрайт-данные в странице W0»
* (docs/sprite-api-design.md §3.1/§9в, обсуждение 2026-07-13).
*
* Идея: атлас грузится в EMM-страницу; на время sprite_update страница
* подключается в W0 (0x0000-0x3FFF). Опасность: ВСЕ прерывания приходят
* на 0x38 текущей страницы W0 (IM1 напрямую; наш IM2-трамплин чейнит
* jp 0x0038). Защита: в первых 0x100 байтах страницы — стаб:
* 0x38: JP w0_isr_stub (W2), 0x66: RETN. Стаб свапит W0 на страницу
* ядра DSS, отдаёт прерывание настоящему 0x38 (подложив возврат) и
* ВОССТАНАВЛИВАЕТ спрайт-страницу.
*
* Стадии (Enter между; PASS/FAIL печатается ПОСЛЕ восстановления W0):
* P1 ESTEX READ в страницу, замапленную в W3 (приём mdview2).
* P2 подготовка страницы: стаб + картинка 16×16 в 0x100 + сентинел.
* P3 IM1: страница в W0, busy-цикл ~2-3 c с EI — выжить, сентинел
* через W0 верен на каждой итерации (restore работает).
* P4 IM2 (irq_install): страница в W0, дождаться 100 кадровых
* прерываний — счётчик растёт => полный тракт tramp→chain→стаб→
* DSS→restore живёт; сентинел верен.
* P5 графика: gfx_blit картинки ИЗ АДРЕСА 0x0100 (accel-burst
* src в W0) банком 0x50; self-check getpixel по ОЗУ-копии.
*
* Сборка: tiny (irq_install требует код/данные в W2) + --gfx 256.
* Запуск через MAME-мост: press_key посимвольно + Enter'ы.
*/
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <graphics.h>
#include <gfx.h>
#include <irq.h>
#include <sprinter.h>
#include <sprinter_mem.h>
#define PAT_LEN 1024 /* P1: объём проверки чтения */
#define SENT_OFF 0x3F00u /* сентинел в конце страницы */
#define SENT_VAL 0xA5
#define IMG_OFF 0x0100u /* данные атласа — после резерва стаба */
#define IMG_W 16
#define IMG_H 16
static uint8_t w2buf[PAT_LEN]; /* эталон в W2 */
static uint8_t blk, phys; /* EMM-блок и физ. страница */
static uint8_t dss_page; /* страница ядра DSS (снята со старта) */
/* Для стаба (читается из прерывания — только W2-данные). */
static uint8_t w0_cur;
/* ISR-стаб (живёт в W2 = _CODE tiny): вызывается JP'ом с 0x38
* спрайт-страницы. Свапит W0 на ядро DSS, отдаёт прерывание честному
* 0x38 (адрес возврата подложен на стек — DSS завершает EI/RETI и
* возвращается в хвост), затем возвращает спрайт-страницу. Вложенное
* прерывание в хвосте (EI уже сделан DSS) безопасно: W0 в этот момент
* = DSS, штатный путь, стаб не реентерится. */
static void w0_isr_stub(void) __naked
{
__asm
push af
ld a, (_dss_page)
out (#0x82), a
pop af
push hl
ld hl, #w0_isr_ret
ex (sp), hl ; вершина стека = возврат, HL цел
jp 0x0038 ; честный обработчик DSS (EI/RETI)
w0_isr_ret:
push af
ld a, (_w0_cur)
out (#0x82), a
pop af
ret
__endasm;
}
/* W0 := A под DI (щель между out и прерыванием закрывается стабом,
* но сам свап делаем атомарно). */
static void w0_set(uint8_t page) __naked
{
(void)page;
__asm
di
out (#0x82), a
ei
ret
__endasm;
}
static void w0_map(void) { w0_cur = phys; w0_set(phys); }
static void w0_unmap(void) { w0_set(dss_page); }
/* ---- P4: счётчик кадровых прерываний -------------------------------- */
static volatile uint8_t irq_ticks;
static void tick_isr(void) { irq_ticks++; }
/* ---- P5: тестовая картинка ------------------------------------------ *
* Рамка RED, тело LIGHTCYAN, центр 2×2 YELLOW. */
static void build_image_via_w3(void)
{
uint8_t *p = (uint8_t *)0xC000 + IMG_OFF;
int x, y;
p[0] = IMG_W; p[1] = 0; p[2] = IMG_H; p[3] = 0;
p += 4;
for (y = 0; y < IMG_H; y++)
for (x = 0; x < IMG_W; x++) {
uint8_t c = LIGHTCYAN;
if (x == 0 || y == 0 || x == IMG_W - 1 || y == IMG_H - 1)
c = RED;
if ((x == 7 || x == 8) && (y == 7 || y == 8))
c = YELLOW;
*p++ = c;
}
}
int main(void)
{
uint8_t saved_w3;
int fd, i, n;
uint8_t ok;
puts("W0PAGE: sprite data in W0 window probes");
dss_page = _io_page_w0; /* IN A,(0x82): страница ядра */
printf("dss page = %u\n", dss_page);
blk = mem_alloc_pages(1);
if (!blk) { puts("alloc FAIL"); return 1; }
phys = mem_get_page(blk, 0);
printf("emm page = %u\n", phys);
/* ---- P1: ESTEX READ в W3-замапленную страницу ------------------- */
for (i = 0; i < PAT_LEN; i++) w2buf[i] = (uint8_t)(i * 7 + 3);
fd = creat("w0page.dat", 0);
if (fd < 0) { puts("creat FAIL"); return 1; }
write(fd, w2buf, PAT_LEN);
close(fd);
fd = open("w0page.dat", O_RDONLY);
saved_w3 = _io_page_w3;
sprinter_page_w3(phys);
n = read(fd, (void *)0xC000, PAT_LEN); /* буфер — в W3! */
close(fd);
ok = (n == PAT_LEN);
if (ok)
for (i = 0; i < PAT_LEN; i++)
if (*((uint8_t *)0xC000 + i) != w2buf[i]) { ok = 0; break; }
/* ---- P2: стаб + картинка + сентинел (страница ещё в W3) --------- */
{
uint8_t *pg = (uint8_t *)0xC000;
uint16_t stub = (uint16_t)&w0_isr_stub;
pg[0x38] = 0xC3; /* JP w0_isr_stub */
pg[0x39] = (uint8_t)(stub & 0xFF);
pg[0x3A] = (uint8_t)(stub >> 8);
pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN (NMI) */
pg[SENT_OFF] = SENT_VAL;
build_image_via_w3();
}
sprinter_page_w3(saved_w3);
printf("P1 read->W3: %s\n", ok ? "PASS" : "FAIL");
puts("P2 page prepared (stub+img)");
(void)getchar();
/* ---- P3: IM1, страница в W0, busy-цикл с EI --------------------- *
* Внутри окна НИКАКИХ ESTEX/BIOS. ~50-100 прерываний IM1 пройдут
* через стаб; сентинел через W0 обязан оставаться верным. */
ok = 1;
w0_map();
{
uint8_t outer;
uint16_t inner;
for (outer = 0; outer < 60; outer++)
for (inner = 0; inner < 40000u; inner++)
if (*(volatile uint8_t *)SENT_OFF != SENT_VAL) ok = 0;
}
w0_unmap();
printf("P3 IM1 in W0: %s\n", ok ? "PASS" : "FAIL");
(void)getchar();
/* ---- P4: IM2, страница в W0, счёт кадровых прерываний ----------- *
* Бюджет ожидания: 100 кадров = 2 с; guard-циклов с запасом ~10 с
* (65000×150Т ≈ 0.5 с — прошлый одиночный цикл истекал раньше
* срока и давал ложный FAIL). */
ok = 0;
{
uint8_t got = 0, sent_ok = 1, outer;
if (irq_install(tick_isr) == 0) {
uint8_t start;
uint16_t guard;
w0_map();
start = irq_ticks;
for (outer = 0; outer < 20 && !ok && sent_ok; outer++)
for (guard = 0; guard < 60000u; guard++) {
got = (uint8_t)(irq_ticks - start);
if (got >= 100) { ok = 1; break; }
if (*(volatile uint8_t *)SENT_OFF != SENT_VAL) {
sent_ok = 0;
break;
}
}
w0_unmap();
irq_remove();
} else
puts("irq_install failed");
printf("P4 IM2 in W0: %s (ticks=%u sent=%s)\n",
ok ? "PASS" : "FAIL", got, sent_ok ? "ok" : "BAD");
}
puts("Enter -> gfx");
(void)getchar();
/* ---- P5: accel-блит из W0-адреса -------------------------------- */
initgraph();
cleardevice();
setfillstyle(SOLID_FILL, GREEN);
bar(140, 90, 175, 125); /* фон вокруг картинки */
w0_map();
gfx_blit(150, 100, (const void *)IMG_OFF); /* src = 0x0100 (W0)! */
w0_unmap();
ok = (getpixel(150, 100) == RED) /* рамка */
&& (getpixel(157, 107) == YELLOW) /* центр */
&& (getpixel(152, 103) == LIGHTCYAN) /* тело */
&& (getpixel(148, 100) == GREEN); /* фон цел */
setcolor(WHITE);
outtextxy(4, 4, "W0PAGE gfx: blit from 0x0100 (W0)");
outtextxy(4, 200, ok ? "P5 blit from W0: PASS"
: "P5 blit from W0: FAIL");
outtextxy(4, 210, "Enter to exit");
(void)getchar();
closegraph();
printf("P5 gfx blit: %s\n", ok ? "PASS" : "FAIL");
puts("done");
return 0;
}