/* * 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 #include #include #include #include #include #include #include #include #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; }