diff --git a/README.md b/README.md index 41fa7ed..95a4a90 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ AT+CIPSNTPTIME? — получить текущее врем - теперь DMA-очистка под индикатор громкости дожидается завершения перед тем, как CPU начинает рисовать поверх той же области (была гонка записи); - исправил переполнение буфера имени файла (`create_filename`): при скачивании имя файла из ссылки копировалось в 48-байтный буфер без проверки длины, и если оно было длиннее — переполнение затирало соседние переменные (`zifi.ini`, имена служебных папок) прямо в памяти. Теперь копирование корректно обрезается на 48 символах. Проверено на железе: создание папки и файла с длинными именами (в т.ч. ровно 48 символов) и последующее чтение содержимого — всё работает штатно, никакой драйверной обрезки до 8 символов нет. +**Известная серьёзная проблема (пока не исправлена): порча имён папок на FAT32.** Штатный драйвер SD-карты (`_sd/WDFCVBI2.COD`, закрытый бинарник без исходников, автор — "Hacker VBI") при создании папки/файла с длинным именем иногда генерирует короткое имя 8.3, совпадающее с уже существующей записью в каталоге — получаются две разные папки с одинаковым коротким именем на диске. Windows chkdsk потом это обнаруживает и переименовывает вторую запись в `<имя>.--N`, а по факту это два разных физических каталога, которые должны были быть одним (например, файлы за один день оказываются раскиданы между `2026_09_01` и `2026_0~1.--0`). Нашёл и подтвердил дизассемблированием конкретный баг в алгоритме генерации числового суффикса `~N` внутри драйвера, но исправить сам драйвер нельзя — исходников нет, только бинарник. Сейчас пишу отдельный, открытый SD/FAT32-драйвер на замену (реализует только те ~15 функций, которые реально использует ZiFi) — работа в процессе, ещё не готово к использованию. + **Известная нерешённая мелочь:** курсор мыши (аппаратный спрайт TSU) обрывается по фиксированной строке экрана прямо над нижней панелью управления — похоже на границу, завязанную на тайминг переключения видеорежима или на аппаратный бордюр, а не на что-то, что двигается вместе с содержимым панели. Я не нашёл способ поправить это без риска сломать что-то ещё, оставил как есть. diff --git a/_spg/ZIFI/zifi.spg b/_spg/ZIFI/zifi.spg index 9828b92..02de339 100644 Binary files a/_spg/ZIFI/zifi.spg and b/_spg/ZIFI/zifi.spg differ diff --git a/zifi.asm b/zifi.asm index b5e68f1..1a55369 100644 --- a/zifi.asm +++ b/zifi.asm @@ -61,6 +61,8 @@ start ld (#beff),hl call gfx_init call set_text_colors + call test_sd_raw ;DIAG: standalone SD read test, remove once verified + call test_sd_raw2 ;DIAG: standalone SD FAT32 file read test, remove once verified call load_ini IF cable_zifi call psb_start @@ -3789,6 +3791,795 @@ parsed_link ds #080 modem_command ds 256+128 current_paging_url ds 150 +;DIAG: standalone raw SD-card hardware test - bypasses WDFCVBI2 entirely, +;DIAG: talks to the SD controller directly via ports CONF=#77/DATA=#57. +;DIAG: Goal: verify our own init+read code can talk to the hardware at all, +;DIAG: before writing more of a replacement driver. Reads LBA 0 and checks +;DIAG: for the standard 55 AA boot-sector signature. Remove after testing. +test_sd_raw + call set_Textpage + ;DIAG: pre-fill the buffer with a recognizable marker before touching + ;DIAG: the card at all -- if this marker survives a "successful" read, + ;DIAG: the read loop isn't actually writing to sdraw_buf + ld hl,sdraw_buf + ld de,sdraw_buf+1 + ld (hl),#ee + ld bc,511 + ldir + + ld a,%00000001 + ld (sdraw_csl_val),a ;DIAG: interface 1 only -- interface 2 (#0B) hung + ;DIAG: the whole machine on real hardware, do not retry it + + di ;DIAG: off_int_dma alone didn't help -- try a real interrupt + ;DIAG: mask, in case int_main pages away our resident slot + ;DIAG: mid-read (not just an SPI timing issue) + call sdraw_init + or a + jp nz,test_sd_raw_noc + + call sdraw_read0 + or a + jp nz,test_sd_raw_rderr + ;DIAG: keep DI held through the prints too now -- ei was previously + ;DIAG: right here, leaving a window for int_main to run between the + ;DIAG: read and the dump; test whether that window is the culprit + + ;DIAG: show the raw command handshake bytes -- R1 response after + ;DIAG: CMD18, and the token byte WTDO settled on (should be #FE) + ld a,(sdraw_last_r1) + ld de,sdraw_hexbuf3 + call sdraw_hexch_pair + ld a,(sdraw_last_token) + ld de,sdraw_hexbuf3+2 + call sdraw_hexch_pair + xor a + ld (sdraw_hexbuf3+4),a + ld (sdraw_hexbuf3+5),a + ld hl,sdraw_r1tok_msg + ld b,1 + call zifi_echo + ld hl,sdraw_hexbuf3 + ld b,1 + call zifi_echo + + ;DIAG: always dump [0..15] and [508..511] before judging the signature, + ;DIAG: so a mismatch shows real vs. garbage/shifted bytes instead of guessing + ld hl,sdraw_buf + ld de,sdraw_hexbuf + ld b,16 + call sdraw_hexdump + ld hl,sdraw_head_msg + ld b,1 + call zifi_echo + ld hl,sdraw_hexbuf + ld b,1 + call zifi_echo + + ld hl,sdraw_buf+508 + ld de,sdraw_hexbuf2 + ld b,4 + call sdraw_hexdump + ld hl,sdraw_tail_msg + ld b,1 + call zifi_echo + ld hl,sdraw_hexbuf2 + ld b,1 + call zifi_echo + + ;DIAG: read LBA0 again, right now, and dump it too -- same bytes as + ;DIAG: above would mean a deterministic bug in our code, not a flaky card + call sdraw_read0 + or a + jr nz,test_sd_raw_rderr + ld hl,sdraw_buf + ld de,sdraw_hexbuf + ld b,16 + call sdraw_hexdump + ld hl,sdraw_head2_msg + ld b,1 + call zifi_echo + ld hl,sdraw_hexbuf + ld b,1 + call zifi_echo + + ld hl,sdraw_buf+510 + ld a,(hl) + cp #55 + jr nz,test_sd_raw_sigerr + inc hl + ld a,(hl) + cp #aa + jr nz,test_sd_raw_sigerr + + ;DIAG: now read a totally different LBA (1000, not 0) -- does the + ;DIAG: garbage pattern shift/change, or stay pinned to the same values? + ld hl,0 + ld de,1000 + call sdraw_readlba + or a + jr nz,test_sd_raw_rderr + ld hl,sdraw_buf + ld de,sdraw_hexbuf + ld b,16 + call sdraw_hexdump + ld hl,sdraw_lba1000_msg + ld b,1 + call zifi_echo + ld hl,sdraw_hexbuf + ld b,1 + call zifi_echo + + ei ;DIAG: only now, after every print, re-enable interrupts + ld hl,sdraw_ok_msg + ld b,1 + call zifi_echo + ret + +test_sd_raw_noc + ei + ld hl,sdraw_noc_msg + ld b,1 + call zifi_echo + ret +test_sd_raw_rderr + ei + ld hl,sdraw_rderr_msg + ld b,1 + call zifi_echo + ret +test_sd_raw_sigerr + ei + ld hl,sdraw_sigerr_msg + ld b,1 + call zifi_echo + ret + +sdraw_noc_msg db 'SD raw test: FAIL, no card found',0,0 +sdraw_rderr_msg db 'SD raw test: FAIL, read error',0,0 +sdraw_sigerr_msg db 'SD raw test: no 55 AA signature (see above)',0,0 +sdraw_ok_msg db 'SD raw test: OK, signature valid',0,0 +sdraw_head_msg db 'sector0[0..15]=',0,0 +sdraw_head2_msg db '2nd read[0..15]=',0,0 +sdraw_lba1000_msg db 'LBA1000[0..15]=',0,0 +sdraw_tail_msg db 'sector0[508..511]=',0,0 +sdraw_r1tok_msg db 'R1,token=',0,0 +sdraw_iface_msg db 'interface CS_ON=',0,0 +sdraw_hexbuf ds 40 +sdraw_hexbuf2 ds 16 +sdraw_hexbuf3 ds 8 +sdraw_last_r1 ds 1 +sdraw_last_token ds 1 + +sdraw_CONF equ #77 +sdraw_DATA equ #57 + +;--- convert A into 2 hex ASCII chars at (DE) --- +sdraw_hexch_pair + push af + rra + rra + rra + rra + call sdraw_hexch + ld (de),a + inc de + pop af + call sdraw_hexch + ld (de),a + ret + +;--- hex dump helper: HL=src, DE=dest ascii buffer, B=byte count --- +sdraw_hexdump + ld a,(hl) + push af + rra + rra + rra + rra + call sdraw_hexch + ld (de),a + inc de + pop af + call sdraw_hexch + ld (de),a + inc de + inc hl + djnz sdraw_hexdump + xor a + ld (de),a + inc de + ld (de),a + ret + +sdraw_hexch + and #0f + cp 10 + jr c,1f + add a,7 +1 add a,'0' + ret + +;--- low-level SD-over-SPI primitives, ported from source/DSDZC.ASM (Wild +;--- Commander Improved) which targets the same ZX Evolution SD controller. +sdraw_csh + push bc + push af + ld bc,sdraw_CONF + ld a,%00000011 + out (c),a + ld bc,sdraw_DATA + ld a,#ff + out (c),a + pop af + pop bc + ret + +sdraw_csl_val db %00000001 ;DIAG: interface 1 by default; test_sd_raw + ;DIAG: flips this to %00001011 (interface 2) + ;DIAG: if interface 1's signature check fails +sdraw_csl + push bc + push af + ld bc,sdraw_CONF + ld a,(sdraw_csl_val) + out (c),a + ld bc,sdraw_DATA + ld a,#ff + out (c),a + pop af + pop bc + jp sdraw_wait + +sdraw_wait + push bc + push af + ld bc,sdraw_DATA + ld de,60000 +1 in a,(c) + inc a + jr z,2f + dec de + ld a,d + or e + jr nz,1b +2 pop af + pop bc + ret + +sdraw_wtdo + push bc + push de + ld bc,sdraw_DATA + ld de,60000 +1 in a,(c) + cp #ff + jr nz,2f + dec de + ld a,d + or e + jr nz,1b +2 pop de + pop bc + ret + +sdraw_resp + push de + push bc + ld bc,sdraw_DATA + ld d,10 +1 in a,(c) + bit 7,a + jr z,2f + dec d + jr nz,1b + inc d +2 pop bc + pop de + ret + +sdraw_cycl ; DE = number of #FF clock bytes to send + ld bc,sdraw_DATA +1 ld a,#ff + out (c),a + dec de + ld a,d + or e + jr nz,1b + ret + +;--- send a pre-built 6-byte command frame pointed to by HL (real CRC). +;--- Matches D:\ST\ZP\PLAY60.ASM's actual shipped timing (not the blog +;--- article, which the article's own author -- who wrote that player -- +;--- says has bugs): two dummy IN (C) reads before the frame ("; savelij" +;--- in the source), and a NOP between each byte sent. Without this, the +;--- handshake (R1/token) still looks valid but the payload comes back as +;--- deterministic garbage -- exactly what we were seeing. +sdraw_cmd + call sdraw_csh + call sdraw_csl + ld bc,sdraw_DATA + in (c) + in (c) + outi + nop + outi + nop + outi + nop + outi + nop + outi + nop + outi + jp sdraw_resp + +;--- send a simple command: A=opcode, all-zero args, CRC=#FF --- +sdraw_cmdo + call sdraw_csh + call sdraw_csl +sdraw_cmdx + ld bc,sdraw_DATA + in (c) + in (c) + out (c),a + xor a + out (c),a + nop + out (c),a + nop + out (c),a + nop + out (c),a + ld a,#ff + out (c),a + ret + +sdraw_cmd00 db %01000000+0,0,0,0,0,#95 +sdraw_cmd08 db %01000000+8,0,0,1,#aa,#87 +sdraw_cmd16 db %01000000+16,0,0,2,0,#ff + +sdraw_do_cmd0 ld hl,sdraw_cmd00 + jp sdraw_cmd +sdraw_do_cmd8 ld hl,sdraw_cmd08 + jp sdraw_cmd +sdraw_do_cmd16 ld hl,sdraw_cmd16 + jp sdraw_cmd +sdraw_do_cmd1 ld a,%01000000+1 + call sdraw_cmdo + jp sdraw_resp +sdraw_do_cmd55 ld a,%01000000+55 + call sdraw_cmdo + jp sdraw_resp + +;--- ACMD41: H = argument high byte (0, or #40 for HCS), rest zero --- +sdraw_do_acmd41 + call sdraw_do_cmd55 + call sdraw_csh + call sdraw_csl + ld bc,sdraw_DATA + in (c) + in (c) + ld a,%01000000+41 + out (c),a + ld l,0 + out (c),h + nop + out (c),l + nop + out (c),l + nop + out (c),l + dec l + out (c),l + jp sdraw_resp + +;--- card init: CMD0/CMD8/ACMD41 (SDv2), falls back to ACMD41/CMD1 (SDv1/MMC) +; A=0 on success, A=1 on failure +sdraw_init + call sdraw_csh + ld de,512+10 + call sdraw_cycl + + ld de,8000 +1 dec de + ld a,d + or e + jp z,sdraw_init_fail + call sdraw_do_cmd0 + jr nz,1b + dec a + jp nz,1b + + call sdraw_do_cmd8 + push af + ld bc,sdraw_DATA + in e,(c) + nop + in e,(c) + nop + in h,(c) + nop + in l,(c) + pop af + jr nz,1b + bit 2,a + jr z,sdraw_v2 + +;--- legacy path: SDv1 (ACMD41) then MMC (CMD1) --- + ld de,8000 +2 dec de + ld a,d + or e + jr z,3f + ld h,0 + call sdraw_do_acmd41 + jr nz,2b + cp 1 + jr z,2b + or a + jr nz,3f + jr sdraw_fbs + +3 ld de,8000 +4 dec de + ld a,d + or e + jr z,sdraw_init_fail + call sdraw_do_cmd1 + jr nz,4b + cp 1 + jr z,4b + or a + jr nz,sdraw_init_fail + +sdraw_fbs call sdraw_do_cmd16 + jr nz,sdraw_init_fail + or a + jr nz,sdraw_init_fail + jr sdraw_init_ok + +;--- SDv2 path: verify the 0x1AA echo pattern from CMD8, then ACMD41+HCS --- +sdraw_v2 ld de,#01aa + or a + sbc hl,de + jr nz,sdraw_init_fail + + ld de,8000 +5 dec de + ld a,d + or e + jr z,sdraw_init_fail + ld h,#40 + call sdraw_do_acmd41 + jr nz,5b + cp 1 + jr z,5b + or a + jr nz,sdraw_init_fail + ; not distinguishing byte/block addressing (CMD58/CCS) here -- + ; irrelevant for reading LBA 0, which is address 0 either way. + +sdraw_init_ok + call sdraw_csh + xor a + ret + +sdraw_init_fail + call sdraw_csh + xor a + inc a + ret + +;--- read LBA 0 (single 512-byte sector) into sdraw_buf --- +; A=0 on success, A=1 on failure +sdraw_read0 + ld hl,0 + ld de,0 + jp sdraw_readlba + +;--- read an arbitrary LBA (single 512-byte sector) via CMD18+CMD12 into +;--- sdraw_buf. Matches the proven reference implementation in D:\ST\ZP's +;--- Z-Player source (SD-CARD2.TXT / PLAY60.ASM) byte-for-byte in approach: +;--- CMD18 (not CMD17!) for the read, and INI (not manual IN+LD(HL),A) for +;--- the bulk 512-byte transfer. +; HL = LBA bits31-16, DE = LBA bits15-0 +; A=0 on success, A=1 on failure +sdraw_readlba + call sdraw_csh + call sdraw_csl + ld bc,sdraw_DATA + in (c) ;DIAG: settling reads + inter-byte NOPs, matching + in (c) ;DIAG: PLAY60.ASM's CMzz exactly (see sdraw_cmd) + ld a,%01000000+18 + out (c),a + nop + out (c),h + nop + out (c),l + nop + out (c),d + nop + out (c),e + ld a,#ff + out (c),a + call sdraw_resp + ld (sdraw_last_r1),a ;DIAG: record for display, whatever the outcome + or a + jr nz,sdraw_readlba_fail + + call sdraw_wtdo + ld (sdraw_last_token),a ;DIAG: record for display, whatever the outcome + cp #fe + jr nz,sdraw_readlba_fail + + ;DIAG: manual IN+store loop, reloading BC (so B is pinned at 0) before + ;DIAG: every single byte -- ruling out B-register drift across INI's + ;DIAG: as a hardware port-decode issue, however unlikely + ld hl,sdraw_buf + ld e,32 +1 ld b,16 +2 ld c,sdraw_DATA + in a,(c) + ld (hl),a + inc hl + djnz 2b + dec e + jr nz,1b + + ld bc,sdraw_DATA + in a,(c) ; discard 2 CRC bytes + in a,(c) + + call sdraw_do_cmd12 + call sdraw_snb + call sdraw_wtby + call sdraw_csh + xor a + ret + +sdraw_readlba_fail + call sdraw_csh + xor a + inc a + ret + +;--- stop a multi-block transfer (CMD12). Sent without a fresh CSH/CSL -- +;--- the card is still selected from the CMD18 that started the transfer. +sdraw_do_cmd12 + ld bc,sdraw_DATA + ld a,%01000000+12 + out (c),a + xor a + out (c),a + out (c),a + out (c),a + out (c),a + ld a,#ff + out (c),a + xor a + in a,(sdraw_DATA) ; one stuff byte before the R1 response, per spec + jp sdraw_resp + +sdraw_snb + push bc + ld b,16 +1 xor a + in a,(sdraw_DATA) + djnz 1b + pop bc + ret + +sdraw_wtby + push bc + ld bc,sdraw_DATA +1 in a,(c) + or a + jr z,1b + pop bc + ret + +;DIAG: step 2 - parse the FAT32 BPB (already validated in sdraw_buf's first +;DIAG: 16 bytes), locate the root directory, find ZIFI.INI's short-name +;DIAG: entry there, and read+print its first line. Proves we can navigate +;DIAG: the filesystem and read a real file, not just a raw sector. +test_sd_raw2 + di ;DIAG: real interrupt mask, see test_sd_raw + call sdraw_read0 + or a + jp nz,t2_fail + + ld a,(sdraw_buf+13) + ld (sdraw_secpclus),a + + ; acc(32-bit) = reservedSectors + ld hl,(sdraw_buf+14) + ld (sdraw_acc_lo),hl + ld hl,0 + ld (sdraw_acc_hi),hl + + ; acc += FATSz32 * numFATs + ld hl,(sdraw_buf+36) + ld (sdraw_fatsz_lo),hl + ld hl,(sdraw_buf+38) + ld (sdraw_fatsz_hi),hl + ld a,(sdraw_buf+16) + ld b,a + or a + jr z,t2_fatdone +t2_fatloop + push bc + ld hl,(sdraw_fatsz_lo) + ld de,(sdraw_fatsz_hi) + call sdraw_acc_add32 + pop bc + djnz t2_fatloop +t2_fatdone + ; acc now = firstDataSector -- save it, needed again for the file itself + ld hl,(sdraw_acc_lo) + ld (sdraw_firstdata_lo),hl + ld hl,(sdraw_acc_hi) + ld (sdraw_firstdata_hi),hl + + ; acc += (rootCluster-2) * sectorsPerCluster + ld hl,(sdraw_buf+44) + ld de,2 + or a + sbc hl,de + ld b,h + ld c,l +t2_clusloop + ld a,b + or c + jr z,t2_clusdone + push bc + ld a,(sdraw_secpclus) + ld l,a + ld h,0 + ld de,0 + call sdraw_acc_add32 + pop bc + dec bc + jr t2_clusloop +t2_clusdone + ld hl,(sdraw_acc_hi) + ld de,(sdraw_acc_lo) + call sdraw_readlba + or a + jp nz,t2_fail + + ld hl,sdraw_buf +t2_scan + ld a,h + cp high (sdraw_buf+512) + jr nz,t2_chk + ld a,l + cp low (sdraw_buf+512) + jr z,t2_notfound +t2_chk + push hl + ld de,sdraw_zifiini_name + ld b,11 +1 ld a,(de) + cp (hl) + jr nz,2f + inc hl + inc de + djnz 1b + pop hl + jr t2_found +2 pop hl + ld de,32 + add hl,de + jr t2_scan + +t2_found + ; extract starting cluster: low word @ entry+26, high word @ entry+20 + push hl + ld de,26 + add hl,de + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + ld (sdraw_filecl_lo),hl + pop hl + push hl + ld de,20 + add hl,de + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + ld (sdraw_filecl_hi),hl + pop hl + + ; acc = firstDataSector + (fileCluster-2)*sectorsPerCluster + ld hl,(sdraw_firstdata_lo) + ld (sdraw_acc_lo),hl + ld hl,(sdraw_firstdata_hi) + ld (sdraw_acc_hi),hl + + ld hl,(sdraw_filecl_lo) + ld de,2 + or a + sbc hl,de + ld b,h + ld c,l +t2_fclusloop + ld a,b + or c + jr z,t2_fclusdone + push bc + ld a,(sdraw_secpclus) + ld l,a + ld h,0 + ld de,0 + call sdraw_acc_add32 + pop bc + dec bc + jr t2_fclusloop +t2_fclusdone + ld hl,(sdraw_acc_hi) + ld de,(sdraw_acc_lo) + call sdraw_readlba + or a + jr nz,t2_fail + + ei + ld hl,sdraw_found_msg + ld b,1 + call zifi_echo + ld hl,sdraw_buf + ld b,1 + call zifi_echo + ret + +t2_notfound + ei + ld hl,sdraw_notfound_msg + ld b,1 + call zifi_echo + ret +t2_fail + ei + ld hl,sdraw_fail2_msg + ld b,1 + call zifi_echo + ret + +; adds a 32-bit value (DE=high word, HL=low word) into the 32-bit +; accumulator sdraw_acc_hi:sdraw_acc_lo +sdraw_acc_add32 + push de + ld de,(sdraw_acc_lo) + add hl,de + ld (sdraw_acc_lo),hl + pop hl + ld de,(sdraw_acc_hi) + adc hl,de + ld (sdraw_acc_hi),hl + ret + +sdraw_found_msg db 'ZIFI.INI found, first line:',0,0 +sdraw_notfound_msg db 'ZIFI.INI not found in root dir sector0',0,0 +sdraw_fail2_msg db 'SD raw test2: FAIL',0,0 +sdraw_zifiini_name db "ZIFI INI" + +sdraw_secpclus ds 1 +sdraw_acc_lo ds 2 +sdraw_acc_hi ds 2 +sdraw_fatsz_lo ds 2 +sdraw_fatsz_hi ds 2 +sdraw_firstdata_lo ds 2 +sdraw_firstdata_hi ds 2 +sdraw_filecl_lo ds 2 +sdraw_filecl_hi ds 2 + + align 256 +sdraw_buf ds 512 +;DIAG: end of standalone raw SD-card test block + sd_init call init_sd_card