diff --git a/_sd/README.md b/_sd/README.md
index 592ed24..80e507b 100644
--- a/_sd/README.md
+++ b/_sd/README.md
@@ -7,7 +7,9 @@
## Файлы
- `zc_sd_driver.asm` — резидентная часть: таблица переходов CORE,
- di/ei-обёртки над каждой точкой входа, распаковщик тела.
+ сохранение IX в точках входа и распаковщик тела. Длинные рабочие операции
+ оставляют растровые прерывания включёнными; конфликтующие задачи ZiFi
+ блокируются снаружи через `init_sd_card`/`sd_exit`.
- `zc_sd_driver_body.asm` — основная логика: SPI/SD-протокол, DMA,
разбор FAT32 (BPB, цепочки кластеров, FAT), поиск по имени с LFN,
создание/удаление файлов и каталогов. Хранится в сжатом виде и
diff --git a/_sd/zc_sd_driver.asm b/_sd/zc_sd_driver.asm
index 8926de6..bc0108a 100644
--- a/_sd/zc_sd_driver.asm
+++ b/_sd/zc_sd_driver.asm
@@ -216,59 +216,46 @@ wrap_save512 push ix
call core_save512
pop ix
ret
-wrap_mkfile di
- push ix
+; Runtime filesystem calls are already bracketed by zifi.asm's
+; init_sd_card/sd_exit. That disables every IRQ job which can repage CPU
+; slots or touch DMA, while leaving the raster interrupts themselves alive.
+; Keeping DI across FAT scans and card busy waits skipped the text-mode raster
+; lines and produced full-height bands / flashes of the backing splash.
+wrap_mkfile push ix
call core_mkfile
pop ix
- ei
ret
-wrap_mkdir di
- push ix
+wrap_mkdir push ix
call core_mkdir
pop ix
- ei
ret
-wrap_fentry di
- push ix
+wrap_fentry push ix
call drv_fentry
pop ix
- ei
ret
-wrap_loadnon di
- push ix
+wrap_loadnon push ix
call drv_loadnon
pop ix
- ei
ret
-wrap_setdir di
- push ix
+wrap_setdir push ix
call core_setdir
pop ix
- ei
ret
-wrap_setroot di
- push ix
+wrap_setroot push ix
call core_setroot
pop ix
- ei
ret
-wrap_seek0 di
- push ix
+wrap_seek0 push ix
call stream_open
pop ix
- ei
ret
-wrap_delfl di
- push ix
+wrap_delfl push ix
call core_delfl
pop ix
- ei
ret
-wrap_renam di
- push ix
+wrap_renam push ix
call core_renam
pop ix
- ei
ret
; DOS_SWP now does real work (depacking the body -- see drv_dos_swp
; below), unlike every other entry here it previously had NO di/ei
diff --git a/_spg/ZIFI/zifi.spg b/_spg/ZIFI/zifi.spg
index a543b7d..5b05450 100644
Binary files a/_spg/ZIFI/zifi.spg and b/_spg/ZIFI/zifi.spg differ
diff --git a/includes.asm b/includes.asm
index 96f278b..773daea 100644
--- a/includes.asm
+++ b/includes.asm
@@ -273,13 +273,19 @@ rfilp4 ld bc,0
ret
ENDIF
IFUSED set_ports
-set_ports ld c,#AF
+set_ports ld a,1
+ ld (dma_busy),a
+ ld c,#AF
.m1 ld b,(hl)
inc hl
inc b
- jr z,dma_stats
+ jr z,.wait
outi
jr .m1
+.wait call dma_stats
+ xor a
+ ld (dma_busy),a
+ ret
ENDIF
IFUSED set_ports_nowait
@@ -393,4 +399,4 @@ SIN_DAT
DB #EC,#EE,#F0,#F2,#F4,#F6,#F7,#F9
DB #FA,#FB,#FC,#FD,#FE,#FE,#FF,#FF
- ENDIF
\ No newline at end of file
+ ENDIF
diff --git a/zifi.asm b/zifi.asm
index 67add63..d5447a4 100644
--- a/zifi.asm
+++ b/zifi.asm
@@ -372,28 +372,28 @@ zifi_get call wait_frame
ld hl,0
ld (readed_len_low+1),hl
ld hl,cmd_conn2site ; AT+CIPSTART - connect to site
- call zifi_send
+ call zifi_send_raw ; response is polled below; do not enter fifo_inir's hidden wait
ld de,str_error
call buffer_cmp
jr nz,zg_ok
ld hl,cmd_cipclose
- call zifi_send
+ call zifi_send_raw
1 ld de,str_ok
call buffer_cmp
jr z,zifi_get
+ call poll_input_fifo
call wait_frame
- call fifo_inir
jr 1b
-zg_ok ld b,15 ; TLS handshakes (SSL connect) can take longer than one poll
+zg_ok ld b,250 ; up to ~5s for a TLS connect reply, without a hidden FIFO wait
zg_ok_wait ld de,str_ok
- call buffer_cmp
+ call buffer_cmp
jr z,zg_ok_done
push bc
+ call poll_input_fifo
call wait_frame
- call fifo_inir
pop bc
djnz zg_ok_wait
jr zifi_get ; still no OK - start the whole connection attempt over
@@ -401,10 +401,12 @@ zg_ok_done
1 ld hl,cmd_cipsend ; AT+CIPSEND=,
call zifi_send_raw
-2 call fifo_inir
+2 call poll_input_fifo
ld de,str_ok
call buffer_cmp
- jr nz,2b
+ jr z,1f
+ call wait_frame
+ jr 2b
1 ld a,(hl)
inc hl
cp #ff
@@ -713,6 +715,19 @@ read_pack_proc cp 0xbf
; push af
ret
+; Read pending ESP bytes only when FIFO already contains data. fifo_inir
+; otherwise calls zifi_input_fifo_check, which may wait 256 frames and used to
+; let the start-up splash show through while an NTP server was silent.
+; BC is preserved because callers can use it as a retry counter.
+poll_input_fifo push bc
+ ld bc,zifi_input_fifo_status
+ in a,(c)
+ or a
+ jr z,.done
+ call fifo_inir
+.done pop bc
+ ret
+
fifo_inir push de
call zifi_input_fifo_check
ld hl,output_buff ; read data from fifo, count: #bf
@@ -2274,6 +2289,12 @@ frame ld a,0
save_mode ld a,1
or a
jr z,int_ex_regs
+ ; Foreground screen updates and the SD driver share the single TS-Conf
+ ; DMA register set with mouse sprite uploads. Never interrupt a DMA
+ ; programming sequence with another one.
+ ld a,(dma_busy)
+ or a
+ jr nz,int_ex_regs
mouse_sw ld a,1
or a
call nz,mouse_proc
@@ -2578,6 +2599,7 @@ ma2 ld (hl),a
jp restore_page3
ay_volume ds 3
analyzer_active db 0
+dma_busy db 0
@@ -3434,7 +3456,12 @@ all_init di
ld hl,splash_copy
jp set_ports
-gfx_init ld hl,cursor_copy
+gfx_init ; The splash is useful only during the initial delay. Clear its
+ ; backing 256-colour pages before installing the normal UI, so a late
+ ; or missed text raster can reveal only black, never the old banner.
+ ld hl,clr_gfx256_screen
+ call set_ports
+ ld hl,cursor_copy
call set_ports
ld hl,menu_copy
call set_ports
@@ -4244,6 +4271,7 @@ cmd_sntp_time db "AT+CIPSNTPTIME?",13,10,0
str_sntp_time db "+CIPSNTPTIME:",0
str_epoch_year db "1970"
sntp_outer_left db 0
+sntp_reply_left db 0
month_names db "JanFebMarAprMayJunJulAugSepOctNovDec"
; Five bounded attempts. On final failure CF=1; caller deliberately falls
@@ -4260,13 +4288,17 @@ ntp_sync_rtc ld b,5
ntp_sync_rtc_once
call clear_input_fifo ; drop stale bytes before reading the reply
ld hl,cmd_sntp_cfg
- call zifi_send
- ld b,15 ; bounded wait for CIPSNTPCFG's own OK
+ call zifi_send_raw ; the bounded loop below owns reception
+ ld a,50 ; one-second, frame-based timeout for CIPSNTPCFG's OK
+ ld (sntp_reply_left),a
sntp_cfg_wait ld de,str_ok
call buffer_cmp
jr z,sntp_cfg_ok
- call fifo_inir
- djnz sntp_cfg_wait
+ call poll_input_fifo ; never call the blocking FIFO waiter while NTP is silent
+ call wait_frame
+ ld hl,sntp_reply_left
+ dec (hl)
+ jr nz,sntp_cfg_wait
scf
ret ; module didn't even accept the config - give up
sntp_cfg_ok
@@ -4275,13 +4307,17 @@ sntp_cfg_ok
sntp_query call clear_input_fifo ; give the background sync a bit of real time each round
ld hl,cmd_sntp_time
- call zifi_send
- ld b,15 ; bounded wait for THIS query's reply to arrive
+ call zifi_send_raw
+ ld a,50 ; one-second, frame-based timeout for THIS reply
+ ld (sntp_reply_left),a
sntp_query_wait ld de,str_sntp_time
call buffer_cmp
jr z,sntp_have_line
- call fifo_inir
- djnz sntp_query_wait
+ call poll_input_fifo
+ call wait_frame
+ ld hl,sntp_reply_left
+ dec (hl)
+ jr nz,sntp_query_wait
scf
ret ; module never answered the query at all - give up
@@ -4309,47 +4345,37 @@ sntp_no_sync scf
ret ; this attempt exhausted its bounded epoch wait
sntp_real_time pop hl ; hl -> string start, this is a real time now
-
- di ; keep CMOS unlock/select/write atomic vs the raster ISR
- ld bc,#eff7 ; #EFF7 is a SHARED system register (video mode/turbo/
- in a,(c) ; cache/memlock bits, not just the CMOS-enable bit) -
- or #80 ; must read-modify-write it, not stomp it with a literal value
- out (c),a
+ push hl
+ call sntp_validate_reply
+ pop hl
+ jr nc,.valid
+ scf ; malformed reply: leave RTC untouched, let outer 5-attempt loop retry
+ ret
+.valid
; register b
- ld a,#0b
- ld b,#df
- out (c),a
- ld a,#82
- ld b,#bf
- out (c),a
+ ld d,#0b
+ ld e,#82
+ call rtc_write_reg
; day of week (3-letter name, offset 0..2), 0=Sunday
push hl
push hl
pop ix
- push bc
call day_of_week_index ; -> a = 0-6, 0=Sunday
- pop bc
pop hl
- push af
- ld a,#06
- ld b,#df
- out (c),a
- ld b,#bf
- pop af
- out (c),a
+ ld d,#06
+ ld e,a
+ call rtc_write_reg
; year (last 2 of the 4 digits, offset 20..23)
push hl
ld de,22
add hl,de
- ld a,#09
- ld b,#df
- out (c),a
- ld b,#bf
call code_time_rtc
- out (c),a
+ ld d,#09
+ ld e,a
+ call rtc_write_reg
pop hl
; month (3-letter name, offset 4..6)
@@ -4358,39 +4384,27 @@ sntp_real_time pop hl ; hl -> string start, this is a real time now
add hl,de
push hl
pop ix
- push bc
call month_to_bcd ; -> a = BCD month
- pop bc
pop hl
- push af
- ld a,#08
- ld b,#df
- out (c),a
- ld b,#bf
- pop af
- out (c),a
+ ld d,#08
+ ld e,a
+ call rtc_write_reg
; date of the month (offset 8..9, space-padded for single digits - and0
; code_time_rtc's "and #0f" happens to treat a leading space as digit 0)
push hl
ld de,8
add hl,de
- ld a,#07
- ld b,#df
- out (c),a
- ld b,#bf
call code_time_rtc
- out (c),a
+ ld d,#07
+ ld e,a
+ call rtc_write_reg
pop hl
; hours (offset 11..12), adjusted by the configured GMT offset
push hl
ld de,11
add hl,de
- ld a,#04
- ld b,#df
- out (c),a
- ld b,#bf
ld a,(hl)
and #0f
@@ -4431,46 +4445,35 @@ gmt_time add 0
rl e
rl e
or e
- out (c),a
+ ld d,#04
+ ld e,a
+ call rtc_write_reg
pop hl
; minutes (offset 14..15)
push hl
ld de,14
add hl,de
- ld a,#02
- ld b,#df
- out (c),a
- ld b,#bf
call code_time_rtc
- out (c),a
+ ld d,#02
+ ld e,a
+ call rtc_write_reg
pop hl
; seconds (offset 17..18)
push hl
ld de,17
add hl,de
- ld a,#00
- ld b,#df
- out (c),a
- ld b,#bf
call code_time_rtc
- out (c),a
+ ld d,#00
+ ld e,a
+ call rtc_write_reg
pop hl
; register b
- ld a,#0b
- ld b,#df
- out (c),a
- ld a,#02
- ld b,#bf
- out (c),a
-
- ld bc,#eff7
- in a,(c)
- and #7f
- out (c),a
- ei
+ ld d,#0b
+ ld e,#02
+ call rtc_write_reg
ld hl,ntp_synced_msg
ld b,1
@@ -4478,8 +4481,130 @@ gmt_time add 0
xor a ; NC = NTP date was written to RTC
ret
+; Validate the complete ESP-AT asctime() payload before changing the RTC.
+; This rejects partial/garbled FIFO replies such as a year "20F0". A bad
+; reply is retryable; it must never turn into a believable but wrong folder.
+; in: HL -> "Www Mmm dd hh:mm:ss yyyy"; out: NC = valid, C = invalid.
+sntp_validate_reply
+ push hl
+
+ ; Exact day-of-week and month names.
+ push hl
+ pop ix
+ call day_of_week_index
+ jr c,.bad
+ pop hl
+ push hl
+ ld de,4
+ add hl,de
+ push hl
+ pop ix
+ call month_to_bcd
+ jr c,.bad
+ pop hl
+
+ ; Day: a space is allowed only for 1..9, otherwise it is 01..31.
+ push hl
+ ld de,8
+ add hl,de
+ call sntp_day_bcd
+ jr c,.bad
+ cp #01
+ jr c,.bad
+ cp #32
+ jr nc,.bad
+ pop hl
+
+ ; Hours/minutes/seconds must be strict decimal pairs in their ranges.
+ push hl
+ ld de,11
+ add hl,de
+ call sntp_bcd2
+ jr c,.bad
+ cp #24
+ jr nc,.bad
+ pop hl
+ push hl
+ ld de,14
+ add hl,de
+ call sntp_bcd2
+ jr c,.bad
+ cp #60
+ jr nc,.bad
+ pop hl
+ push hl
+ ld de,17
+ add hl,de
+ call sntp_bcd2
+ jr c,.bad
+ cp #60
+ jr nc,.bad
+ pop hl
+
+ ; RTC holds a two-digit year, but accept only a real 20xx date (2020-2099).
+ push hl
+ ld de,20
+ add hl,de
+ ld a,(hl)
+ cp "2"
+ jr nz,.bad
+ inc hl
+ ld a,(hl)
+ cp "0"
+ jr nz,.bad
+ inc hl
+ call sntp_bcd2
+ jr c,.bad
+ cp #20
+ jr c,.bad
+ pop hl
+ or a
+ ret
+.bad pop hl
+ scf
+ ret
+
+; Strict decimal pair at HL -> BCD. HL is left after the two characters.
+sntp_bcd2 ld a,(hl)
+ sub "0"
+ jr c,.bad
+ cp 10
+ jr nc,.bad
+ rlca
+ rlca
+ rlca
+ rlca
+ ld b,a
+ inc hl
+ ld a,(hl)
+ sub "0"
+ jr c,.bad
+ cp 10
+ jr nc,.bad
+ or b
+ or a
+ ret
+.bad scf
+ ret
+
+; Day accepts the ESP's space-padded single-digit form (" 5") too.
+sntp_day_bcd ld a,(hl)
+ cp " "
+ jr nz,sntp_bcd2
+ inc hl
+ ld a,(hl)
+ sub "1"
+ jr c,.bad
+ cp 9
+ jr nc,.bad
+ inc a
+ or a
+ ret
+.bad scf
+ ret
+
; in: ix -> 3-letter month name (e.g. from CIPSNTPTIME's reply)
-; out: a = BCD month 01-12 (clobbers af,bc,de,hl); defaults to January if not found
+; out: a = BCD month 01-12; C set if the name is not an English month.
month_to_bcd ld hl,month_names
ld c,1
mtb_loop ld a,(ix+0)
@@ -4502,16 +4627,18 @@ mtb_next ld de,3
jr z,mtb_notfound
inc c
jr mtb_loop
-mtb_notfound ld c,1
+mtb_notfound scf
+ ret
mtb_found ld a,c
cp 10
jr c,mtb_done
sub 10
add a,#10
-mtb_done ret
+mtb_done or a
+ ret
; in: ix -> 3-letter day-of-week name (e.g. from CIPSNTPTIME's reply)
-; out: a = 0-6, 0=Sunday (clobbers af,bc,de,hl); defaults to Sunday if not found
+; out: a = 0-6, 0=Sunday (clobbers af,bc,de,hl); C set if not found
day_of_week_index
ld hl,dow_names
ld c,0
@@ -4535,25 +4662,65 @@ dow_next ld de,3
jr z,dow_notfound
inc c
jr dow_loop
-dow_notfound ld c,0 ; ran out of names - default to Sunday
+dow_notfound scf
+ ret
dow_found ld a,c
+ or a
ret
dow_names db "SunMonTueWedThuFriSat"
ntp_synced_msg db "Time synced via NTP.",0,0
+; One RTC register per atomic section. The old single DI around the whole
+; NTP conversion suppressed the raster chain and exposed splash_copy.
+; D = CMOS register, E = value for write; clobbers AF/BC/DE.
+rtc_write_reg di
+ ld bc,#eff7
+ in a,(c)
+ or #80
+ out (c),a
+ ld a,d
+ ld b,#df
+ out (c),a
+ ld a,e
+ ld b,#bf
+ out (c),a
+ ld bc,#eff7
+ in a,(c)
+ and #7f
+ out (c),a
+ ei
+ ret
+
+; A = CMOS register, returns A = value; preserves DE, clobbers BC.
+rtc_read_reg push de
+ di
+ ld e,a
+ ld bc,#eff7
+ in a,(c)
+ or #80
+ out (c),a
+ ld a,e
+ ld b,#df
+ out (c),a
+ ld b,#bf
+ in a,(c)
+ ld e,a
+ ld bc,#eff7
+ in a,(c)
+ and #7f
+ out (c),a
+ ld a,e
+ pop de
+ ei
+ ret
+
; Reads the current date straight back off the RTC chip into DIR_date, as
; "20YY_MM_DD" - used to name the downloads/ folder. Works regardless of
; whether "time:" is "none" or the NTP sync above failed: the chip keeps
; ticking on its own battery either way, so this is the one source of truth.
rtc_read_date
- di
- ld bc,#eff7
- in a,(c)
- or #80
- out (c),a
-
ld de,DIR_date+1
ld a,"2"
ld (de),a
@@ -4563,37 +4730,22 @@ rtc_read_date
inc de
; year
ld a,#09
- ld b,#df
- out (c),a
- ld b,#bf
- in a,(c)
+ call rtc_read_reg
call bcd_to_ascii
ld a,"_"
ld (de),a
inc de
; month
ld a,#08
- ld b,#df
- out (c),a
- ld b,#bf
- in a,(c)
+ call rtc_read_reg
call bcd_to_ascii
ld a,"_"
ld (de),a
inc de
; date of the month
ld a,#07
- ld b,#df
- out (c),a
- ld b,#bf
- in a,(c)
+ call rtc_read_reg
call bcd_to_ascii
-
- ld bc,#eff7
- in a,(c)
- and #7f
- out (c),a
- ei
ret
bcd_to_ascii ; in: a = BCD byte, de = destination; writes 2 ASCII digits, advances de