Fix FAT chain termination and harden SD driver error handling

This commit is contained in:
2026-10-07 22:53:51 +03:00
parent 0e300bf5fd
commit b5856a7787
8 changed files with 1002 additions and 443 deletions
+424 -421
View File
@@ -1,403 +1,406 @@
body_end: EQU 0x00001FAA body_end: EQU 0x00001FE7
cmkf_open_stream: EQU 0x00001F75 cmkf_open_stream: EQU 0x00001FB2
cmkf_final: EQU 0x00001F5F cmkf_final: EQU 0x00001F9C
cmkf_buildlfn: EQU 0x00001F43 cmkf_buildlfn: EQU 0x00001F80
cmkf_entry: EQU 0x00001F01 cmkf_entry: EQU 0x00001F3E
cmkf_havecl: EQU 0x00001ED3 cmkf_havecl: EQU 0x00001F10
cmkf_alloc: EQU 0x00001EBD cmkf_alloc: EQU 0x00001EFA
cmkf_havelen: EQU 0x00001EA4 cmkf_havelen: EQU 0x00001EE1
cmkf_lendone: EQU 0x00001E9B cmkf_lendone: EQU 0x00001ED8
cmkf_lenloop: EQU 0x00001E8E cmkf_lenloop: EQU 0x00001ECB
core_mkfile: EQU 0x00001E73 core_mkfile: EQU 0x00001EB0
cmkf_len_lo: EQU 0x00001E71 cmkf_len_lo: EQU 0x00001EAE
cmkf_len_hi: EQU 0x00001E6F cmkf_len_hi: EQU 0x00001EAC
cmkd_final: EQU 0x00001E5A cmkd_final: EQU 0x00001E97
cmkd_buildlfn: EQU 0x00001E3E cmkd_buildlfn: EQU 0x00001E7B
cmkd_ownentry: EQU 0x00001E0B cmkd_ownentry: EQU 0x00001E48
dotdot_name: EQU 0x00001F91 dotdot_name: EQU 0x00001FCE
cmkd_dot_ok: EQU 0x00001DBF cmkd_dot_ok: EQU 0x00001DFC
dot_name: EQU 0x00001F86 dot_name: EQU 0x00001FC3
clear_dir_entry: EQU 0x00001F9C clear_dir_entry: EQU 0x00001FD9
cmkd_dots: EQU 0x00001D6B cmkd_dots: EQU 0x00001DA8
cmkd_zf: EQU 0x00001D55 cmkd_zf: EQU 0x00001D92
cmkd_have_cl: EQU 0x00001D27 cmkd_have_cl: EQU 0x00001D64
cmkd_alloc: EQU 0x00001D11 cmkd_alloc: EQU 0x00001D4E
cmkd_toolong: EQU 0x00001CF0 cmkd_toolong: EQU 0x00001D2D
cmkd_lendone: EQU 0x00001CF4 cmkd_lendone: EQU 0x00001D31
core_mkdir: EQU 0x00001CE3 core_mkdir: EQU 0x00001D20
cmk_sec_lo: EQU 0x00001CE1 cmk_sec_lo: EQU 0x00001D1E
cmk_sec_hi: EQU 0x00001CDF cmk_sec_hi: EQU 0x00001D1C
cmk_cluster_lo: EQU 0x00001CDD cmk_cluster_lo: EQU 0x00001D1A
cmk_cluster_hi: EQU 0x00001CDB cmk_cluster_hi: EQU 0x00001D18
cmk_name_ptr: EQU 0x00001CD9 cmk_name_ptr: EQU 0x00001D16
sv512_nowrap: EQU 0x00001CD5 sv512_nowrap: EQU 0x00001D12
lv512_nowrap: EQU 0x00001CA8 lv512_nowrap: EQU 0x00001CE5
lv512_advance: EQU 0x00001C92 lv512_advance: EQU 0x00001CCF
lv512_eoc: EQU 0x00001CB5 lv512_eoc: EQU 0x00001CF2
lv512_done: EQU 0x00001CAC lv512_done: EQU 0x00001CE9
lv512_loop: EQU 0x00001C71 lv512_loop: EQU 0x00001CAE
core_load512: EQU 0x00001C66 core_load512: EQU 0x00001CA3
lv512_count: EQU 0x00001C65 lv512_count: EQU 0x00001CA2
lv512_off: EQU 0x00001C63 lv512_off: EQU 0x00001CA0
lv512_page: EQU 0x00001C62 lv512_page: EQU 0x00001C9F
sv512_advance: EQU 0x00001CBF sv512_advance: EQU 0x00001CFC
sv512_eoc: EQU 0x00001C58 sv512_eoc: EQU 0x00001C95
sv512_done: EQU 0x00001C4F sv512_done: EQU 0x00001C8C
sv512_loop: EQU 0x00001C2E sv512_loop: EQU 0x00001C6B
core_save512: EQU 0x00001C23 core_save512: EQU 0x00001C60
sv512_count: EQU 0x00001C22 sv512_count: EQU 0x00001C5F
page1_port: EQU 0x000011AF page1_port: EQU 0x000011AF
core_fentry: EQU 0x00001C0E core_fentry: EQU 0x00001C4B
core_setdir: EQU 0x00001C01 core_setdir: EQU 0x00001C3E
core_setroot: EQU 0x00001BF4 core_setroot: EQU 0x00001C31
active_dir_lo: EQU 0x00001BF2 active_dir_lo: EQU 0x00001C2F
active_dir_hi: EQU 0x00001BF0 active_dir_hi: EQU 0x00001C2D
fbn_tm_yes: EQU 0x00001BB2 fbn_tm_yes: EQU 0x00001BEF
fbn_tm_short: EQU 0x00001BA0 fbn_tm_short: EQU 0x00001BDD
fbn_acc_null: EQU 0x00001B72 fbn_acc_null: EQU 0x00001BAF
fbn_acc_pad: EQU 0x00001B7D fbn_acc_pad: EQU 0x00001BBA
fbn_acc_charloop: EQU 0x00001B46 fbn_acc_charloop: EQU 0x00001B83
fbn_acc_mul: EQU 0x00001B36 fbn_acc_mul: EQU 0x00001B73
fbn_acc_mul_done: EQU 0x00001B3C fbn_acc_mul_done: EQU 0x00001B79
fbn_matched_setrun: EQU 0x00001B07 fbn_matched_setrun: EQU 0x00001B44
fbn_matched_norun: EQU 0x00001B04 fbn_matched_norun: EQU 0x00001B41
fbn_accumulate_lfn: EQU 0x00001B0D fbn_accumulate_lfn: EQU 0x00001B4A
fbn_lfn_cont: EQU 0x00001ACE fbn_lfn_cont: EQU 0x00001B0B
fbn_next: EQU 0x00001AD9 fbn_next: EQU 0x00001B16
fbn_matched: EQU 0x00001AF2 fbn_matched: EQU 0x00001B2F
fbn_try_match: EQU 0x00001B80 fbn_try_match: EQU 0x00001BBD
fbn_is_lfn: EQU 0x00001AC0 fbn_is_lfn: EQU 0x00001AFD
fbn_deleted: EQU 0x00001AD3 fbn_deleted: EQU 0x00001B10
fbn_notfound_pop: EQU 0x00001AEF fbn_notfound_pop: EQU 0x00001B2C
fbn_entloop: EQU 0x00001A9F fbn_entloop: EQU 0x00001ADC
fbn_notfound: EQU 0x00001AF0 fbn_notfound: EQU 0x00001B2D
fbn_sector_loop: EQU 0x00001A7F fbn_sector_loop: EQU 0x00001ABC
find_by_name: EQU 0x00001A6F find_by_name: EQU 0x00001AAC
fbn_match_run_start_lo: EQU 0x00001A6D fbn_match_run_start_lo: EQU 0x00001AAA
fbn_match_index_lo: EQU 0x00001A6B fbn_match_index_lo: EQU 0x00001AA8
fbn_run_start_lo: EQU 0x00001A69 fbn_run_start_lo: EQU 0x00001AA6
fbn_cur_index_lo: EQU 0x00001A67 fbn_cur_index_lo: EQU 0x00001AA4
fbn_render_buf: EQU 0x00001A5A fbn_render_buf: EQU 0x00001A97
fbn_attr: EQU 0x00001A59 fbn_attr: EQU 0x00001A96
fbn_size_lo: EQU 0x00001A57 fbn_size_lo: EQU 0x00001A94
fbn_size_hi: EQU 0x00001A55 fbn_size_hi: EQU 0x00001A92
fbn_cluster_hi: EQU 0x00001A51 fbn_cluster_hi: EQU 0x00001A8E
fbn_acc_dest: EQU 0x00001A4F fbn_acc_dest: EQU 0x00001A8C
fbn_lfn_seen: EQU 0x00001A4E fbn_lfn_seen: EQU 0x00001A8B
fbn_lfn_checksum: EQU 0x00001A4D fbn_lfn_checksum: EQU 0x00001A8A
fbn_lfn_buf: EQU 0x000019E4 fbn_lfn_buf: EQU 0x00001A21
fbn_name_ptr: EQU 0x000019E2 fbn_name_ptr: EQU 0x00001A1F
rsn_ext: EQU 0x000019D4 rsn_ext: EQU 0x00001A11
rsn_done: EQU 0x000019DE rsn_done: EQU 0x00001A1B
rsn_ext_prep: EQU 0x000019C9 rsn_ext_prep: EQU 0x00001A06
rsn_base_skip: EQU 0x000019C6 rsn_base_skip: EQU 0x00001A03
rsn_base: EQU 0x000019BA rsn_base: EQU 0x000019F7
render_shortname: EQU 0x000019B7 render_shortname: EQU 0x000019F4
seci_eq: EQU 0x000019B5 seci_eq: EQU 0x000019F2
seci_ne: EQU 0x000019B2 seci_ne: EQU 0x000019EF
str_eq_ci: EQU 0x0000199F str_eq_ci: EQU 0x000019DC
to_upper: EQU 0x00001996 to_upper: EQU 0x000019D3
wne_w_skip_write: EQU 0x0000198D wne_w_skip_write: EQU 0x000019CA
wne_w_notinrange: EQU 0x0000195F wne_w_notinrange: EQU 0x0000199C
wne_w_entloop: EQU 0x00001929 wne_w_entloop: EQU 0x00001966
wne_w_fail: EQU 0x00001993 wne_w_fail: EQU 0x000019D0
wne_w_continue: EQU 0x000018F4 wne_w_continue: EQU 0x00001931
wne_w_done: EQU 0x000018F2 wne_w_done: EQU 0x0000192F
wne_w_sector_loop: EQU 0x000018DD wne_w_sector_loop: EQU 0x0000191A
write_name_entries: EQU 0x000018D4 write_name_entries: EQU 0x00001911
wne_w_this_sec_lo: EQU 0x000018D2 wne_w_this_sec_lo: EQU 0x0000190F
wne_w_this_sec_hi: EQU 0x000018D0 wne_w_this_sec_hi: EQU 0x0000190D
wne_w_dirty: EQU 0x000018CF wne_w_dirty: EQU 0x0000190C
wne_w_entcount: EQU 0x000018CE wne_w_entcount: EQU 0x0000190B
wne_w_entptr: EQU 0x000018CC wne_w_entptr: EQU 0x00001909
ffr_no_hi_carry: EQU 0x0000184C ffr_no_hi_carry: EQU 0x00001889
ffr_free_cont: EQU 0x00001815 ffr_free_cont: EQU 0x00001852
ffr_advance_index: EQU 0x00001836 ffr_advance_index: EQU 0x00001873
ffr_free: EQU 0x000017FF ffr_free: EQU 0x0000183C
ffr_entloop: EQU 0x000017F0 ffr_entloop: EQU 0x0000182D
ffr_fail: EQU 0x00001859 ffr_grow_dir: EQU 0x00001899
ffr_grow_dir: EQU 0x0000185C ffr_have_sector: EQU 0x00001815
ffr_have_sector: EQU 0x000017D8 ffr_fail: EQU 0x00001896
ffr_sector_loop: EQU 0x000017C9 ffr_sector_loop: EQU 0x00001801
find_free_run: EQU 0x000017B9 find_free_run: EQU 0x000017F1
wne_free_index_lo: EQU 0x000017B7 wne_free_index_lo: EQU 0x000017EF
wne_free_index_hi: EQU 0x000017B5 wne_free_index_hi: EQU 0x000017ED
cmkd_lenloop: EQU 0x00001CE8 cmkd_lenloop: EQU 0x00001D25
wne_cur_index_lo: EQU 0x000017B3 wne_cur_index_lo: EQU 0x000017EB
wne_cur_index_hi: EQU 0x000017B1 wne_cur_index_hi: EQU 0x000017E9
wne_run_start_lo: EQU 0x000017AF wne_run_start_lo: EQU 0x000017E7
wne_run_start_hi: EQU 0x000017AD wne_run_start_hi: EQU 0x000017E5
wne_run_len: EQU 0x000017AC wne_run_len: EQU 0x000017E4
ble_char_nextk: EQU 0x00001779 ble_char_nextk: EQU 0x000017B1
ble_char_hi0: EQU 0x00001777 ble_char_hi0: EQU 0x000017AF
ble_char_have: EQU 0x0000175D ble_char_have: EQU 0x00001795
ble_char_isreal: EQU 0x00001753 ble_char_isreal: EQU 0x0000178B
ble_char_isnull: EQU 0x0000175C ble_char_isnull: EQU 0x00001794
ble_lfn_next: EQU 0x0000177C ble_lfn_next: EQU 0x000017B4
ble_charloop: EQU 0x00001734 ble_charloop: EQU 0x0000176C
ble_mul13done: EQU 0x0000172F ble_mul13done: EQU 0x00001767
ble_mul13: EQU 0x00001724 ble_mul13: EQU 0x0000175C
ble_seqset: EQU 0x00001706 ble_seqset: EQU 0x0000173E
ble_notlast: EQU 0x00001705 ble_notlast: EQU 0x0000173D
ble_lfn_done: EQU 0x00001783 ble_lfn_done: EQU 0x000017BB
ble_lfn_loop: EQU 0x000016CE ble_lfn_loop: EQU 0x00001706
ble_divdone: EQU 0x000016B7 ble_divdone: EQU 0x000016EF
ble_divloop: EQU 0x000016AC ble_divloop: EQU 0x000016E4
ble_multi: EQU 0x000016A3 ble_multi: EQU 0x000016DB
build_lfn_entries: EQU 0x0000167E build_lfn_entries: EQU 0x000016B6
ble_entry_ptr: EQU 0x0000167C ble_entry_ptr: EQU 0x000016B4
ble_charpos: EQU 0x0000167A ble_charpos: EQU 0x000016B2
ble_j: EQU 0x00001679 ble_j: EQU 0x000016B1
ble_checksum: EQU 0x00001678 ble_checksum: EQU 0x000016B0
ble_lfn_count: EQU 0x00001677 ble_lfn_count: EQU 0x000016AF
ble_name_ptr: EQU 0x00001675 ble_name_ptr: EQU 0x000016AD
lfn_char_dest: EQU 0x00001668 lfn_char_dest: EQU 0x000016A0
wne_slot_count: EQU 0x00001667 wne_slot_count: EQU 0x0000169F
wne_entries_buf: EQU 0x00001547 wne_entries_buf: EQU 0x0000157F
wne_max_lfn: EQU 0x00000008 wne_max_lfn: EQU 0x00000008
btd_done: EQU 0x0000153B btd_done: EQU 0x00001573
btd_loop: EQU 0x00001532 btd_loop: EQU 0x0000156A
gus_have_candidate: EQU 0x00001511 gus_have_candidate: EQU 0x00001549
bin_to_2dig: EQU 0x00001530 bin_to_2dig: EQU 0x00001568
gus_try_1digit: EQU 0x000014F9 gus_try_1digit: EQU 0x00001531
gus_try: EQU 0x000014D4 gus_try: EQU 0x0000150C
gus_ok: EQU 0x0000152E gus_ok: EQU 0x00001566
gus_needs_suffix: EQU 0x000014CF gus_needs_suffix: EQU 0x00001507
generate_unique_shortname: EQU 0x000014B0 generate_unique_shortname: EQU 0x000014E8
gus_suffix: EQU 0x000014AF gus_suffix: EQU 0x000014E7
fn83_ext_full: EQU 0x000014A1 fn83_ext_full: EQU 0x000014D9
fn83_ext_bad: EQU 0x000014A8 fn83_ext_bad: EQU 0x000014E0
fn83_ext_loop: EQU 0x00001483 fn83_ext_loop: EQU 0x000014BB
fn83_ext_go: EQU 0x0000146C fn83_ext_go: EQU 0x000014A4
fn83_base_full: EQU 0x00001451 fn83_base_full: EQU 0x00001489
fn83_base_bad: EQU 0x00001458 fn83_base_bad: EQU 0x00001490
fn83_ext: EQU 0x0000145F fn83_ext: EQU 0x00001497
fn83_base_loop: EQU 0x00001432 fn83_base_loop: EQU 0x0000146A
fn83_have_baselen: EQU 0x00001427 fn83_have_baselen: EQU 0x0000145F
fn83_scan_next: EQU 0x000013E5 fn83_scan_next: EQU 0x0000141D
fn83_scandone: EQU 0x000013E9 fn83_scandone: EQU 0x00001421
fn83_scan: EQU 0x000013D9 fn83_scan: EQU 0x00001411
filter_name_to_83: EQU 0x000013CE filter_name_to_83: EQU 0x00001406
fn83_charbuf: EQU 0x000013CD fn83_charbuf: EQU 0x00001405
sn_base_len: EQU 0x000013CB sn_base_len: EQU 0x00001403
sn_dot_pos: EQU 0x000013C9 sn_dot_pos: EQU 0x00001401
sn_name_len: EQU 0x000013C7 sn_name_len: EQU 0x000013FF
sn_needs_lfn: EQU 0x000013C6 sn_needs_lfn: EQU 0x000013FE
sn_ext: EQU 0x000013C3 sn_ext: EQU 0x000013FB
sn_base: EQU 0x000013BB sn_base: EQU 0x000013F3
cc_symyes: EQU 0x000013B6 cc_symyes: EQU 0x000013EE
cc_no: EQU 0x000013B9 cc_no: EQU 0x000013F1
cc_symloop: EQU 0x000013AC cc_symloop: EQU 0x000013E4
cc_sym: EQU 0x000013A8 cc_sym: EQU 0x000013E0
cc_yes: EQU 0x000013B7 cc_yes: EQU 0x000013EF
cc_dig: EQU 0x000013A0 cc_dig: EQU 0x000013D8
cc_up: EQU 0x00001398 cc_up: EQU 0x000013D0
classify_char: EQU 0x0000138E classify_char: EQU 0x000013C6
cc_symbols: EQU 0x0000137D cc_symbols: EQU 0x000013B5
lfnck_loop: EQU 0x00001377 lfnck_loop: EQU 0x000013AF
lfn_checksum: EQU 0x00001374 lfn_checksum: EQU 0x000013AC
stamp_dir_entry_datetime: EQU 0x0000115D stamp_dir_entry_datetime: EQU 0x00001195
rtc_to_fat_datetime: EQU 0x000010E1 rtc_to_fat_datetime: EQU 0x00001119
rtc_cached: EQU 0x000010E0 rtc_cached: EQU 0x00001118
fat_time: EQU 0x000010DE fat_time: EQU 0x00001116
fat_date: EQU 0x000010DC fat_date: EQU 0x00001114
bcd_to_bin: EQU 0x000010C7 bcd_to_bin: EQU 0x000010FF
rtc_field_to_bin: EQU 0x000010BB rtc_field_to_bin: EQU 0x000010F3
rtc_dm: EQU 0x000010BA rtc_dm: EQU 0x000010F2
rtc_year: EQU 0x00001049 rtc_year: EQU 0x00001081
rtc_month: EQU 0x00001048 rtc_month: EQU 0x00001080
rtc_date: EQU 0x00001047 rtc_date: EQU 0x0000107F
rtc_hour: EQU 0x00001046 rtc_hour: EQU 0x0000107E
rtc_min: EQU 0x00001045 rtc_min: EQU 0x0000107D
rtc_sec: EQU 0x00001044 rtc_sec: EQU 0x0000107C
sv512_off: EQU 0x00001C20 sv512_off: EQU 0x00001C5D
sv512_page: EQU 0x00001C1F sv512_page: EQU 0x00001C5C
strm_write_have2: EQU 0x0000102F strm_write_have2: EQU 0x00001067
stream_write_sector_from: EQU 0x00000FCB stream_write_sector_from: EQU 0x00001000
read_rtc_datetime: EQU 0x0000104A read_rtc_datetime: EQU 0x00001082
strm_write_have: EQU 0x00000FB6 strm_write_have: EQU 0x00000FEB
stream_write_sector: EQU 0x00000F48 stream_write_sector: EQU 0x00000F7A
stream_read_sector_to: EQU 0x00000D2D stream_read_sector_to: EQU 0x00000D5F
stream_data_save: EQU 0x00000D48 stream_data_save: EQU 0x00000D7A
strm_read_eoc: EQU 0x00000D2A strm_read_eoc: EQU 0x00000D5C
stream_read_sector: EQU 0x00000CF7 stream_read_sector: EQU 0x00000D29
strm_adv_same: EQU 0x00000CF1 strm_adv_same: EQU 0x00000D23
strm_adv_cross: EQU 0x00000CCA strm_adv_cross: EQU 0x00000CFC
strm_adv_test_z: EQU 0x00000CC2 strm_adv_test_z: EQU 0x00000CF4
strm_advance: EQU 0x00000CB5 strm_advance: EQU 0x00000CE7
strm_calc_sector: EQU 0x00000C8D strm_calc_sector: EQU 0x00000CBF
strm_open_nz: EQU 0x00000C79 strm_open_nz: EQU 0x00000CAB
stream_open: EQU 0x00000C5F stream_open: EQU 0x00000C91
strm_sector_lo: EQU 0x00000C5D strm_sector_lo: EQU 0x00000C8F
strm_sector_hi: EQU 0x00000C5B strm_sector_hi: EQU 0x00000C8D
stream_eoc: EQU 0x00000C5A stream_eoc: EQU 0x00000C8C
stream_sec_in_cluster: EQU 0x00000C59 stream_sec_in_cluster: EQU 0x00000C8B
stream_cluster_lo: EQU 0x00000C57 stream_cluster_lo: EQU 0x00000C89
stream_cluster_hi: EQU 0x00000C55 stream_cluster_hi: EQU 0x00000C87
wde_next: EQU 0x00000C24 wde_next: EQU 0x00000C56
wde_slot: EQU 0x00000C35 wde_slot: EQU 0x00000C67
wde_entloop: EQU 0x00000C04 wde_entloop: EQU 0x00000C36
wde_fail: EQU 0x00000C52 wde_fail: EQU 0x00000C84
wde_sec_loop: EQU 0x00000BEF wde_sec_loop: EQU 0x00000C21
wde_cluster: EQU 0x00000BDA wde_cluster: EQU 0x00000C0C
write_dir_entry: EQU 0x00000BDA write_dir_entry: EQU 0x00000C0C
wde_slotptr: EQU 0x00000BD8 wde_slotptr: EQU 0x00000C0A
wde_sector_lo: EQU 0x00000BD6 wde_sector_lo: EQU 0x00000C08
wde_sector_hi: EQU 0x00000BD4 wde_sector_hi: EQU 0x00000C06
wde_remaining: EQU 0x00000BD3 wde_remaining: EQU 0x00000C05
dir_entry_buf: EQU 0x00000BB3 dir_entry_buf: EQU 0x00000BE5
zfc_loop: EQU 0x00000BA1 zfc_loop: EQU 0x00000BD3
zfc_fail: EQU 0x00000BB0 zfc_fail: EQU 0x00000BE2
zero_fill_cluster: EQU 0x00000B7B zero_fill_cluster: EQU 0x00000BAD
zfc_remaining: EQU 0x00000B7A zfc_remaining: EQU 0x00000BAC
ffc_nowrap: EQU 0x00000B6B ffc_nowrap: EQU 0x00000B9D
ffc_nsv2: EQU 0x00000B45 ffc_nsv2: EQU 0x00000B77
ffc_nsv: EQU 0x00000B3F ffc_nsv: EQU 0x00000B71
ffc_entnext: EQU 0x00000B4F ffc_entnext: EQU 0x00000B81
ffc_entloop: EQU 0x00000B08 ffc_entloop: EQU 0x00000B3A
ffc_fail: EQU 0x00000B77 ffc_fail: EQU 0x00000BA9
ffc_from2: EQU 0x00000AC1 ffc_from2: EQU 0x00000AF3
ffc_scan: EQU 0x00000AD0 ffc_scan: EQU 0x00000B02
ffc_use_hint: EQU 0x00000AA7 ffc_use_hint: EQU 0x00000AD9
find_free_cluster: EQU 0x00000A8C find_free_cluster: EQU 0x00000ABE
next_search_valid: EQU 0x00000A87 next_search_valid: EQU 0x00000AB9
ffc_sectors_read: EQU 0x00000A85 ffc_sectors_read: EQU 0x00000AB7
ffc_count: EQU 0x00000A84 ffc_count: EQU 0x00000AB6
ffc_ptr: EQU 0x00000A82 ffc_ptr: EQU 0x00000AB4
free_cluster_lo: EQU 0x00000A80 free_cluster_lo: EQU 0x00000AB2
free_cluster_hi: EQU 0x00000A7E free_cluster_hi: EQU 0x00000AB0
next_search_hi: EQU 0x00000A88 next_search_hi: EQU 0x00000ABA
next_search_lo: EQU 0x00000A8A next_search_lo: EQU 0x00000ABC
wfi_skip_count: EQU 0x00000A62 wfi_skip_count: EQU 0x00000A94
write_fsinfo_hint: EQU 0x00000A27 write_fsinfo_hint: EQU 0x00000A59
read_fsinfo_hint: EQU 0x000009C8 read_fsinfo_hint: EQU 0x000009FA
fsinfo_valid: EQU 0x000009C7 fsinfo_valid: EQU 0x000009F9
fsinfo_hint_lo: EQU 0x000009C5 fsinfo_hint_lo: EQU 0x000009F7
fsinfo_hint_hi: EQU 0x000009C3 fsinfo_hint_hi: EQU 0x000009F5
wfe_loop: EQU 0x0000098C wfe_loop: EQU 0x000009BE
wfe_fail: EQU 0x000009BE wfe_fail: EQU 0x000009F0
write_fat_entry: EQU 0x00000940 write_fat_entry: EQU 0x00000972
wfe_sector_lo: EQU 0x0000093E wfe_sector_lo: EQU 0x00000970
wfe_sector_hi: EQU 0x0000093C wfe_sector_hi: EQU 0x0000096E
wfe_remaining: EQU 0x0000093B wfe_remaining: EQU 0x0000096D
new_val_lo: EQU 0x00000939 new_val_lo: EQU 0x0000096B
new_val_hi: EQU 0x00000937 new_val_hi: EQU 0x00000969
gnc_have_next: EQU 0x00000926 gnc_valid: EQU 0x00000958
gnc_end: EQU 0x00000920 gnc_stop: EQU 0x00000942
nxt_hi: EQU 0x000008DC gnc_end: EQU 0x0000093C
nxt_lo: EQU 0x000008DA gnc_have_next: EQU 0x00000946
ctf_bad: EQU 0x000008D6 gnc_bad: EQU 0x00000940
ctf_shr: EQU 0x00000884 nxt_hi: EQU 0x000008E8
cluster_to_fatpos: EQU 0x00000876 nxt_lo: EQU 0x000008E6
gnc_inoff: EQU 0x00000874 ctf_bad: EQU 0x000008E2
gnc_fatsec_lo: EQU 0x00000872 ctf_shr: EQU 0x00000890
gnc_fatsec_hi: EQU 0x00000870 cluster_to_fatpos: EQU 0x00000882
gnc_lo: EQU 0x0000086E gnc_inoff: EQU 0x00000880
gnc_hi: EQU 0x0000086C gnc_fatsec_lo: EQU 0x0000087E
c2s_loop: EQU 0x00000832 gnc_fatsec_hi: EQU 0x0000087C
c2s_done: EQU 0x0000084B gnc_lo: EQU 0x0000087A
cur_cluster_hi: EQU 0x00000868 gnc_hi: EQU 0x00000878
cur_cluster_lo: EQU 0x0000086A c2s_loop: EQU 0x0000083E
c2s_prod_hi: EQU 0x00000805 c2s_done: EQU 0x00000857
c2s_prod_lo: EQU 0x00000803 cur_cluster_hi: EQU 0x00000874
c2s_hi: EQU 0x00000801 cur_cluster_lo: EQU 0x00000876
c2s_lo: EQU 0x000007FF c2s_prod_hi: EQU 0x00000811
chain_ended: EQU 0x000008D9 c2s_prod_lo: EQU 0x0000080F
get_next_cluster: EQU 0x000008DE c2s_hi: EQU 0x0000080D
fza_next: EQU 0x000007E9 c2s_lo: EQU 0x0000080B
fza_inc_loop: EQU 0x000007E1 chain_ended: EQU 0x000008E5
fza_done: EQU 0x000007FC get_next_cluster: EQU 0x000008EA
fza_stop: EQU 0x000007FA fza_next: EQU 0x000007F5
fza_loop: EQU 0x000007CB fza_inc_loop: EQU 0x000007ED
fza_sector_lo: EQU 0x00000807 fza_done: EQU 0x00000808
fza_sector_hi: EQU 0x00000809 fza_stop: EQU 0x00000806
cluster_to_sector: EQU 0x0000080B fza_loop: EQU 0x000007D7
fza_cluster: EQU 0x000007B8 fza_sector_lo: EQU 0x00000813
find_zifi_all: EQU 0x000007B8 fza_sector_hi: EQU 0x00000815
fza_remaining: EQU 0x000007B7 cluster_to_sector: EQU 0x00000817
fz_found: EQU 0x00000782 fza_cluster: EQU 0x000007C4
fz_cmpfail: EQU 0x00000775 find_zifi_all: EQU 0x000007C4
fz_cmp: EQU 0x00000769 fza_remaining: EQU 0x000007C3
fz_skip: EQU 0x00000777 fz_found: EQU 0x0000078E
fz_end: EQU 0x0000077F fz_cmpfail: EQU 0x00000781
fz_loop: EQU 0x0000075A fz_cmp: EQU 0x00000775
find_zifi: EQU 0x00000755 fz_skip: EQU 0x00000783
target_name: EQU 0x0000074A fz_end: EQU 0x0000078B
zifi_size: EQU 0x00000748 fz_loop: EQU 0x00000766
fbn_acc_nocs: EQU 0x00001B21 find_zifi: EQU 0x00000761
zifi_size_hi: EQU 0x00000746 target_name: EQU 0x00000756
zifi_cluster: EQU 0x00000744 zifi_size: EQU 0x00000754
zifi_cluster_hi: EQU 0x00000742 fbn_acc_nocs: EQU 0x00001B5E
root_dir_hi: EQU 0x00000740 zifi_size_hi: EQU 0x00000752
root_dir_lo: EQU 0x0000073E zifi_cluster: EQU 0x00000750
mul2_loop: EQU 0x000006D2 zifi_cluster_hi: EQU 0x0000074E
mul2_done: EQU 0x000006EB root_dir_hi: EQU 0x0000074C
prod2_hi: EQU 0x0000073C root_dir_lo: EQU 0x0000074A
prod2_lo: EQU 0x0000073A mul2_loop: EQU 0x000006DE
rc2_hi: EQU 0x00000738 mul2_done: EQU 0x000006F7
rc2_lo: EQU 0x00000736 prod2_hi: EQU 0x00000748
data_start_hi: EQU 0x00000734 prod2_lo: EQU 0x00000746
data_start_lo: EQU 0x00000732 rc2_hi: EQU 0x00000744
mul1_loop: EQU 0x0000067B rc2_lo: EQU 0x00000742
mul1_done: EQU 0x00000694 data_start_hi: EQU 0x00000740
prod_hi: EQU 0x00000730 data_start_lo: EQU 0x0000073E
prod_lo: EQU 0x0000072E mul1_loop: EQU 0x00000687
root_cluster_lo: EQU 0x0000072A mul1_done: EQU 0x000006A0
root_cluster_hi: EQU 0x0000072C prod_hi: EQU 0x0000073C
fatsz32_lo: EQU 0x00000726 prod_lo: EQU 0x0000073A
fatsz32_hi: EQU 0x00000728 root_cluster_lo: EQU 0x00000736
sectors_per_cluster: EQU 0x00000725 root_cluster_hi: EQU 0x00000738
num_fats: EQU 0x00000724 fatsz32_lo: EQU 0x00000732
fsinfo_sector: EQU 0x000009C1 fatsz32_hi: EQU 0x00000734
reserved_sectors: EQU 0x00000722 sectors_per_cluster: EQU 0x00000731
total_sectors_hi: EQU 0x0000071C num_fats: EQU 0x00000730
total_sectors_lo: EQU 0x0000071A fsinfo_sector: EQU 0x000009F3
parse_bpb: EQU 0x0000062F reserved_sectors: EQU 0x0000072E
vf_ok: EQU 0x0000062A total_sectors_hi: EQU 0x00000728
vf_bad: EQU 0x0000062C total_sectors_lo: EQU 0x00000726
verify_fat32: EQU 0x000005EA parse_bpb: EQU 0x0000063B
dp_fallback: EQU 0x000005CE vf_ok: EQU 0x00000636
dp_found: EQU 0x0000059F vf_bad: EQU 0x00000638
dp_entry: EQU 0x000005E8 verify_fat32: EQU 0x000005F6
dp_loop: EQU 0x00000585 dp_fallback: EQU 0x000005DA
dp_none: EQU 0x0000059D dp_found: EQU 0x000005AB
detect_partition: EQU 0x0000055A dp_entry: EQU 0x000005F4
sec0_copy: EQU 0x0000035A dp_loop: EQU 0x00000591
add_addtop: EQU 0x0000034A dp_none: EQU 0x000005A9
addtop_lo: EQU 0x00000348 detect_partition: EQU 0x00000566
addtop_hi: EQU 0x00000346 sec0_copy: EQU 0x00000366
cmdx: EQU 0x00000313 add_addtop: EQU 0x00000356
do_cmd55: EQU 0x000002ED addtop_lo: EQU 0x00000354
cmdo: EQU 0x0000030D addtop_hi: EQU 0x00000352
send_frame: EQU 0x000002F5 cmdx: EQU 0x0000031F
cmd16: EQU 0x000002C5 do_cmd55: EQU 0x000002F9
cmd08: EQU 0x000002BF cmdo: EQU 0x00000319
cmd00: EQU 0x000002B9 send_frame: EQU 0x00000301
sdwrite_lba_from: EQU 0x00000234 cmd16: EQU 0x000002D1
sdwrite_lba: EQU 0x00000231 cmd08: EQU 0x000002CB
sdwrite_multi_stop: EQU 0x0000021C cmd00: EQU 0x000002C5
sdwrite_multi_block_from: EQU 0x000001F7 sdwrite_lba_from: EQU 0x0000023E
sdwrite_multi_block: EQU 0x000001F4 sdwrite_lba: EQU 0x0000023B
csl: EQU 0x00000258 sdwrite_multi_stop: EQU 0x00000226
wait: EQU 0x0000026D sdwrite_multi_block_from: EQU 0x00000201
sdwrite_multi_start: EQU 0x000001AF sdwrite_multi_block: EQU 0x000001FE
wtdo: EQU 0x00000282 csl: EQU 0x00000262
sdread_fail: EQU 0x000001A6 wait: EQU 0x00000277
resp: EQU 0x00000298 sdwrite_multi_start: EQU 0x000001B9
sdread_common: EQU 0x0000018D wtdo: EQU 0x0000028E
sdread_lba_oob: EQU 0x000001AC sdread_fail: EQU 0x000001B0
send_cmd_lba: EQU 0x000001C7 resp: EQU 0x000002A4
cmdlba_op: EQU 0x000001C6 sdread_common: EQU 0x00000197
sdread_lba_to: EQU 0x00000182 sdread_lba_oob: EQU 0x000001B6
sdread_lba: EQU 0x0000017F send_cmd_lba: EQU 0x000001D1
dma_send_ext: EQU 0x0000015F cmdlba_op: EQU 0x000001D0
dma_wait: EQU 0x00000179 sdread_lba_to: EQU 0x0000018C
dma_recv_ext: EQU 0x00000143 sdread_lba: EQU 0x00000189
dma_set_addr_ext: EQU 0x0000012D dma_send_ext: EQU 0x00000169
sdbuf: EQU 0x00001174 dma_wait: EQU 0x00000183
set_dma_sdbuf: EQU 0x00000121 dma_recv_ext: EQU 0x0000014D
dma_xfer_page: EQU 0x00000120 dma_set_addr_ext: EQU 0x00000137
dma_xfer_off: EQU 0x0000011E sdbuf: EQU 0x000011AC
set_dma_sdbuf: EQU 0x0000012B
dma_xfer_page: EQU 0x0000012A
dma_xfer_off: EQU 0x00000128
dma_ctr: EQU 0x000027AF dma_ctr: EQU 0x000027AF
dma_num: EQU 0x000028AF dma_num: EQU 0x000028AF
dma_len: EQU 0x000026AF dma_len: EQU 0x000026AF
@@ -408,27 +411,27 @@ dma_daddrx: EQU 0x00001FAF
dma_daddrh: EQU 0x00001EAF dma_daddrh: EQU 0x00001EAF
dma_daddrl: EQU 0x00001DAF dma_daddrl: EQU 0x00001DAF
own_page: EQU 0x0000000F own_page: EQU 0x0000000F
cca_shift: EQU 0x000000F4 cca_shift: EQU 0x000000FE
compute_card_arg: EQU 0x000000E2 compute_card_arg: EQU 0x000000EC
card_arg: EQU 0x000000DE card_arg: EQU 0x000000E8
sd_lba_max_hi: EQU 0x00000720 sd_lba_max_hi: EQU 0x0000072C
sd_lba_max_lo: EQU 0x0000071E sd_lba_max_lo: EQU 0x0000072A
lba_guard_check: EQU 0x000000BF lba_guard_check: EQU 0x000000C9
lba_arg: EQU 0x000000BB lba_arg: EQU 0x000000C5
set_lba32: EQU 0x000000AA set_lba32: EQU 0x000000B4
sd_block_addressed: EQU 0x000000DD sd_v2_ocr_rest: EQU 0x0000009D
sd_v2_ocr_rest: EQU 0x00000099 do_cmd58: EQU 0x000002E9
do_cmd58: EQU 0x000002DD sdinit_ok: EQU 0x000000A9
sdinit_ok: EQU 0x0000009F do_cmd16: EQU 0x000002E3
do_cmd16: EQU 0x000002D7 do_cmd1: EQU 0x000002F1
do_cmd1: EQU 0x000002E5 sd_fbs: EQU 0x00000061
sd_fbs: EQU 0x0000005D do_acmd41: EQU 0x00000332
do_acmd41: EQU 0x00000326 sd_v2: EQU 0x0000006B
sd_v2: EQU 0x00000067 do_cmd8: EQU 0x000002DD
do_cmd8: EQU 0x000002D1 do_cmd0: EQU 0x000002D7
do_cmd0: EQU 0x000002CB sdinit_fail: EQU 0x000000AE
sdinit_fail: EQU 0x000000A4 cycl: EQU 0x000002B8
cycl: EQU 0x000002AC csh: EQU 0x0000024F
csh: EQU 0x00000245 sd_block_addressed: EQU 0x000000E7
sdinit: EQU 0x00000000 sdinit: EQU 0x00000000
fbn_cluster_lo: EQU 0x00001A53 fbn_cluster_lo: EQU 0x00001A90
+49
View File
@@ -0,0 +1,49 @@
# Проверки драйвера
Тесты исполняют собранный машинный код в Python-пакете `z80`.
Данные диска синтетические; физические SD-карты и образы эмулятора
не открываются и не изменяются.
Из `_sd` после сборки тела, обновления `body_syms.inc` и упаковки
PackBits по инструкции в основном README драйвера:
```text
sjasmplus.exe zc_sd_driver_body.asm --sym=body_syms_new.txt
# При изменении тела обновить body_syms.inc и .pb до следующей команды.
sjasmplus.exe zc_sd_driver.asm --sym=resident_syms_new.txt
python tests/test_driver.py
python tests/test_resident.py
```
Нужны Python и пакет `z80`. Бинарники, символьные таблицы и PackBits
должны относиться к одной сборке.
Покрытие (17 групп):
- все 128 смещений FAT32-записей, зеркалирование FAT и сохранение
старших зарезервированных битов;
- полное сравнение EOC, маскирование верхних четырёх битов,
свободные/зарезервированные/повреждённые ссылки и ошибки чтения;
- чтение и запись фрагментированных цепочек, размеры кластера
1–128 секторов, несколько вызовов LOAD512/SAVE512, переходы страниц
памяти с ненулевым начальным смещением;
- выделение кластеров, расширение файла и каталога, отказ от продления
повреждённой цепочки;
- 32-битная арифметика LBA, границы тома, SDSC/SDHC-адресация;
- MBR с разделом в каждом из четырёх слотов, BPB без MBR,
геометрия FAT32 и базовые проверки BPB;
- MKDIR/MKFILE, короткие и длинные имена, коллизии коротких имён,
поиск без учёта регистра и отказ от создания дубликата;
- FSInfo, упаковка даты/времени RTC в BCD и двоичном режиме;
- значения регистров DMA и последовательности команд инициализации
SDHC, SDv2 SDSC, legacy SD и MMC, включая повторную инициализацию
и исчерпание попыток при постоянно занятой карте;
- распаковка тела Z80-кодом, FENTRY, переименование, удаление,
освобождение цепочки и сохранение IX в обёртке MKFILE.
Границы проверки: секторный SPI/DMA-обмен подменяется блоковым
устройством в памяти. В отдельных тестах инициализации, DMA-регистров
и RTC используются модели портов. Электрические характеристики,
аппаратный тайминг, прерывания ZiFi и поведение при отключении питания
этими тестами не проверяются. Тесты не доказывают транзакционность
операций FAT или защиту от всех вариантов повреждённого тома.
+362
View File
@@ -0,0 +1,362 @@
"""Broader machine-code regression tests; run after assembling the body.
Run from _sd: python tests/test_driver.py
Uses synthetic FAT32 sectors only; no physical card or emulator image is changed.
"""
import random
from test_fat_offsets import Driver, SYMS, test_offsets_and_links, test_write_and_allocation, test_fragmented_load
def u32(d, prefix):
return d.get(prefix+'_lo') | d.get(prefix+'_hi') << 16
def put32(d, prefix, value):
d.put(prefix+'_lo', value & 65535)
d.put(prefix+'_hi', value >> 16)
def test_eoc_boundaries():
valid = [2, 63, 64, 127, 128, 65535, 65536,
0x0ff7ffff, 0x0ff80000, 0x0fffffef]
for top in (0, 0xa0000000, 0xf0000000):
for value in valid + list(range(0x0ffffff8, 0x10000000)) + [0, 1] + list(range(0x0ffffff0, 0x0ffffff8)):
d = Driver()
d.fat(100, value | top)
d.cluster(100)
d.call('get_next_cluster')
expected = 0 if value in valid else (1 if value >= 0x0ffffff8 else 2)
assert d.get('chain_ended', 1) == expected, hex(value | top)
assert u32(d, 'cur_cluster') == (value if expected == 0 else 100)
def test_errors_do_not_extend_chain():
for broken in (0, 1, 0x0ffffff0, 0x0ffffff7, 'io'):
d = Driver()
d.fat(100, 101 if broken == 'io' else broken)
d.cluster(100)
d.call('stream_open')
d.put('stream_sec_in_cluster', 1, 1)
if broken == 'io':
d.fail_reads.add(49)
d.call('strm_advance')
assert d.get('stream_eoc', 1) == 2
before = len(d.io)
for routine in ('stream_write_sector', 'stream_write_sector_from', 'stream_read_sector_to'):
assert d.call(routine) != 0
assert len(d.io) == before, 'I/O after broken chain'
d = Driver()
d.cluster(8192) # first cluster outside a 64-sector FAT
d.call('get_next_cluster')
assert d.get('chain_ended', 1) == 2 and not d.io
for routine in ('core_load512', 'core_save512'):
d = Driver()
d.cluster(100)
d.call('stream_open')
d.fail_reads.add(1213)
d.fail_writes.add(1213)
d.m.c, d.m.hl, d.m.b = 32, 0x4000, 1
assert d.call(routine) == 15
assert (d.m.c, d.m.hl) == (32, 0x4000)
assert d.get('stream_sec_in_cluster', 1) == 0
def test_address_arithmetic():
rng = random.Random(589)
for _ in range(300):
d = Driver()
cluster = rng.randrange(2, 0x400000)
spc = rng.choice((1, 2, 4, 8, 16, 32, 64, 128))
data, base = rng.randrange(32, 200000), rng.randrange(0, 200000)
d.cluster(cluster)
d.put('sectors_per_cluster', spc, 1)
put32(d, 'data_start', data)
put32(d, 'addtop', base)
d.call('cluster_to_sector')
assert u32(d, 'fza_sector') == base+data+(cluster-2)*spc
maximum = rng.randrange(1, 0xffffffff)
put32(d, 'sd_lba_max', maximum)
for lba in (0, maximum-1, maximum, 0xffffffff):
d.m.hl, d.m.de = lba >> 16, lba & 65535
d.call('set_lba32')
d.call('lba_guard_check')
assert bool(d.m.f & 1) == (lba < maximum)
for block, lba in ((0, rng.randrange(0x400000)), (1, rng.randrange(0xffffffff))):
d.put('sd_block_addressed', block, 1)
d.m.hl, d.m.de = lba >> 16, lba & 65535
d.call('set_lba32')
d.call('compute_card_arg')
p = SYMS['card_arg']
assert int.from_bytes(d.m.memory[p:p+4], 'big') == lba*(1 if block else 512)
def boot_sector(spc=64):
b = bytearray(512)
for offset, size, value in ((11, 2, 512), (13, 1, spc), (14, 2, 32),
(16, 1, 2), (32, 4, 10000000), (36, 4, 59430),
(44, 4, 12345), (48, 2, 1)):
b[offset:offset+size] = value.to_bytes(size, 'little')
b[510:512] = b'\x55\xaa'
return b
def test_bpb_and_partition():
for spc in (1, 2, 4, 8, 16, 32, 64, 128):
d = Driver()
d.m.set_memory_block(SYMS['sdbuf'], boot_sector(spc))
assert d.call('verify_fat32') == 0
d.call('parse_bpb')
assert u32(d, 'data_start') == 32+2*59430
assert u32(d, 'root_dir') == 32+2*59430+(12345-2)*spc
assert u32(d, 'sd_lba_max') == 17+10000000
for offset, value in ((510, 0), (511, 0), (11, 1), (22, 1), (17, 1), (13, 0), (13, 3)):
d = Driver()
b = boot_sector()
b[offset] = value
d.m.set_memory_block(SYMS['sdbuf'], b)
assert d.call('verify_fat32') != 0, offset
for slot in range(4):
d = Driver()
mbr = bytearray(512)
mbr[510:512] = b'\x55\xaa'
mbr[446+slot*16+4] = 0x0c
mbr[446+slot*16+8:446+slot*16+12] = (70000).to_bytes(4, 'little')
d.disk[70000] = boot_sector()
d.m.set_memory_block(SYMS['sdbuf'], mbr)
assert d.call('detect_partition') == 0
assert u32(d, 'addtop') == 70000
d = Driver()
original = boot_sector()
d.m.set_memory_block(SYMS['sdbuf'], original)
assert d.call('detect_partition') == 0
assert u32(d, 'addtop') == 0
def test_stream_read_write_all_cluster_sizes():
rng = random.Random(731)
for spc in (1, 2, 4, 8, 16, 32, 64, 128):
d = Driver()
d.put('sectors_per_cluster', spc, 1)
chain = [319, 500, 320, 1023]
for i, n in enumerate(chain):
d.fat(n, chain[i+1] if i+1 < len(chain) else 0x0ffffff8)
for s in range(spc):
d.disk[1017+(n-2)*spc+s] = bytearray(512)
payload = rng.randbytes(len(chain)*spc*512)
start = 32*16384+4 # nonzero, even offset; exercise page overflow remainder
d.ram[start:start+len(payload)] = payload
d.cluster(chain[0])
d.call('stream_open')
d.m.c, d.m.hl = 32, 0x4004
remaining = len(payload)//512
while remaining:
count = min(remaining, 127)
d.m.b = count
assert d.call('core_save512') == 0
remaining -= count
on_disk = b''.join(d.disk[1017+(n-2)*spc+s] for n in chain for s in range(spc))
assert on_disk == payload, spc
d.ram[start:start+len(payload)] = bytes(len(payload))
d.cluster(chain[0])
d.call('stream_open')
d.m.c, d.m.hl = 32, 0x4004
remaining = len(payload)//512
while remaining:
count = min(remaining, 113)
d.m.b = count
assert d.call('core_load512') == 0
remaining -= count
assert d.ram[start:start+len(payload)] == payload, spc
d.cluster(chain[0])
d.call('stream_open')
for s in range(spc):
assert d.call('stream_read_sector') == 0
p = SYMS['sdbuf']
assert bytes(d.m.memory[p:p+512]) == payload[s*512:(s+1)*512]
def test_grow_fragmented_file():
d = Driver()
for n in range(256, 512):
d.fat(n, 0x0fffffff)
free = [320, 356, 383, 400]
for n in free:
d.fat(n, 0)
d.put('next_search_valid', 1, 1)
d.put('next_search_lo', 320)
d.cluster(319)
d.call('stream_open')
payload = random.Random(52).randbytes(10*512)
d.ram[32*16384:32*16384+len(payload)] = payload
d.m.c, d.m.hl, d.m.b = 32, 0x4000, 10
assert d.call('core_save512') == 0
chain = [319]+free
actual = b''.join(d.disk[1017+(n-2)*2+s] for n in chain for s in range(2))
assert actual == payload
for index, n in enumerate(chain):
sector, offset = divmod(n*4, 512)
value = int.from_bytes(d.disk[49+sector][offset:offset+4], 'little') & 0x0fffffff
assert value == (chain[index+1] if index+1 < len(chain) else 0x0fffffff)
assert d.disk[49+sector] == d.disk[113+sector]
def test_directories_and_long_names():
d = Driver()
d.put('rtc_cached', 1, 1)
d.put('fat_date', 0x5d47)
d.put('fat_time', 0x6d5d)
for sector in range(49, 113):
d.disk[sector] = bytearray(512)
d.fat(2, 0x0fffffff)
d.put('root_cluster_lo', 2)
d.call('core_setroot')
d.disk[1017] = bytearray(512)
d.disk[1018] = bytearray(512)
d.put('next_search_valid', 1, 1)
d.put('next_search_lo', 320)
def arg(data):
d.m.set_memory_block(0x9000, data + b'\0')
d.m.hl = 0x9000
arg(b'Long directory name')
assert d.call('core_mkdir') == 0
arg(b'long DIRECTORY name')
assert d.call('core_fentry') == 1
directory = u32(d, 'fbn_cluster')
assert directory == 320
first = d.disk[1017+(directory-2)*2]
assert first[:11] == b'. '
assert first[32:43] == b'.. '
assert int.from_bytes(first[58:60], 'little') == 2
d.call('core_setdir')
# Multiple colliding short names, LFN runs crossing sectors, directory growth.
names = [f'Long music filename number {i:02d}.pt3'.encode() for i in range(18)]
for name in names:
arg(b'\0'+(12345).to_bytes(4, 'little')+name)
assert d.call('core_mkfile') == 0, name
clusters = set()
for name in names:
arg(name.upper())
assert d.call('core_fentry') == 1, name
assert u32(d, 'fbn_size') == 12345
clusters.add(u32(d, 'fbn_cluster'))
assert len(clusters) == len(names)
before = {n: bytes(b) for n, b in d.disk.items()}
arg(b'\0'+(12345).to_bytes(4, 'little')+names[0])
assert d.call('core_mkfile') == 3
assert before == d.disk
def test_fsinfo():
for free_count in (0xffffffff, 100000, 65536):
d = Driver()
addr = SYMS['write_fsinfo_hint']
d.m.clear_breakpoint(addr)
del d.hooks[addr]
b = bytearray(512)
for offset, value in ((0, 0x41615252), (484, 0x61417272),
(488, free_count), (492, 65535)):
b[offset:offset+4] = value.to_bytes(4, 'little')
d.disk[18] = b
d.put('fsinfo_sector', 1)
d.call('read_fsinfo_hint')
assert d.get('fsinfo_valid', 1) == 1
assert u32(d, 'fsinfo_hint') == 65535
put32(d, 'next_search', 65536)
assert d.call('write_fsinfo_hint') == 0
assert int.from_bytes(d.disk[18][492:496], 'little') == 65536
assert int.from_bytes(d.disk[18][488:492], 'little') == (free_count if free_count == 0xffffffff else free_count-1)
def test_rtc_encoding():
for binary in (False, True):
d = Driver()
rtc = {0: 59, 2: 42, 4: 23, 7: 7, 8: 10, 9: 26}
selected = [0]
def output(port, value):
if port == 0xdff7:
selected[0] = value
def input_(port):
if port != 0xbff7:
return 0
if selected[0] == 11:
return 4 if binary else 0
value = rtc.get(selected[0], 0)
return value if binary else (value//10)*16+value%10
d.m.set_output_callback(output)
d.m.set_input_callback(input_)
d.call('rtc_to_fat_datetime')
assert d.get('fat_date') == ((2026-1980)<<9 | 10<<5 | 7)
assert d.get('fat_time') == (23<<11 | 42<<5 | 59//2)
def test_dma_registers():
for routine, base, control in (('dma_recv_ext', 0x1daf, 0x42), ('dma_send_ext', 0x1aaf, 0xc2)):
for offset in (0x4000, 0x4004, 0x7ffe):
d = Driver()
ports = {}
d.m.set_output_callback(lambda port, value: ports.__setitem__(port, value))
d.m.set_input_callback(lambda port: 0)
d.put('dma_xfer_off', offset)
d.put('dma_xfer_page', 32, 1)
d.call(routine)
assert ports == {base: offset & 255, base+256: (offset>>8)&63,
base+512: 32, 0x26af: 255, 0x28af: 0, 0x27af: control}
def test_sd_initialization():
from collections import deque
d = Driver()
for kind in ('sdhc', 'sdsc', 'legacy', 'mmc', 'busy'):
commands, frame, response = [], [], deque()
selected = [False]
def output(port, value):
if port & 255 == 0x77:
selected[0] = value == 1
if not selected[0]:
frame.clear()
response.clear()
if port & 255 != 0x57 or not selected[0]:
return
if not frame and not 0x40 <= value <= 0x7f:
return
frame.append(value)
if len(frame) != 6:
return
command = frame[0] & 63
commands.append((command, int.from_bytes(bytes(frame[1:5]), 'big')))
assert len(commands) < 16010, 'Initialization retry counter never expires'
answers = {0: [1], 8: [1, 0, 0, 1, 0xaa] if kind in ('sdhc', 'sdsc', 'busy') else [5],
55: [1], 41: [1 if kind == 'busy' else (5 if kind == 'mmc' else 0)], 1: [0], 16: [0],
58: [0, 0xc0 if kind == 'sdhc' else 0x80, 0xff, 0x80, 0]}
response.extend(answers[command])
frame.clear()
def input_(port):
return response.popleft() if response else 255
d.m.set_output_callback(output)
d.m.set_input_callback(input_)
result = d.call('sdinit')
if kind == 'busy':
assert result != 0
continue
assert result == 0, kind
assert d.get('sd_block_addressed', 1) == int(kind == 'sdhc'), kind
ids = [c for c, _ in commands]
if kind in ('sdhc', 'sdsc'):
assert ids.index(41) < ids.index(58)
if kind != 'sdhc':
assert (16, 512) in commands, (kind, commands)
if __name__ == '__main__':
tests = [value for name, value in list(globals().items()) if name.startswith('test_')]
failures = []
for test in tests:
try:
test()
print('PASS:', test.__name__, flush=True)
except Exception as exc:
import traceback
traceback.print_exc()
failures.append(test.__name__)
assert not failures, failures
print(f'{len(tests)} test groups passed')
+32 -4
View File
@@ -20,9 +20,16 @@ class Driver:
self.m = z80.Z80Machine() self.m = z80.Z80Machine()
self.m.set_memory_block(0, BODY) self.m.set_memory_block(0, BODY)
self.disk = {} self.disk = {}
self.fail_reads = set()
self.fail_writes = set()
self.io = []
self.ram = bytearray(256 * 16384) self.ram = bytearray(256 * 16384)
self.hooks = {SYMS[n]: n for n in self.hooks = {SYMS[n]: n for n in
('sdread_lba', 'sdread_lba_to', 'sdwrite_lba', 'write_fsinfo_hint')} ('sdread_lba', 'sdread_lba_to', 'sdwrite_lba',
'sdwrite_lba_from', 'write_fsinfo_hint')}
self.hooks.update({SYMS[n]: n for n in
('sdwrite_multi_start', 'sdwrite_multi_block', 'sdwrite_multi_stop')})
self.multi_lba = None
for address in [0xff00, *self.hooks]: for address in [0xff00, *self.hooks]:
self.m.set_breakpoint(address) self.m.set_breakpoint(address)
self.put('reserved_sectors', 32) self.put('reserved_sectors', 32)
@@ -59,9 +66,28 @@ class Driver:
hook = self.hooks.get(self.m.pc) hook = self.hooks.get(self.m.pc)
if not hook: if not hook:
continue continue
if hook != 'write_fsinfo_hint': failed = False
if hook == 'sdwrite_multi_start':
p = SYMS['lba_arg']
self.multi_lba = int.from_bytes(self.m.memory[p:p+4], 'big')
elif hook == 'sdwrite_multi_stop':
self.multi_lba = None
elif hook == 'sdwrite_multi_block':
assert self.multi_lba is not None
p = SYMS['sdbuf']
self.disk[self.multi_lba] = bytearray(self.m.memory[p:p+512])
self.io.append((hook, self.multi_lba))
self.multi_lba += 1
elif hook != 'write_fsinfo_hint':
lba = int.from_bytes(self.m.memory[SYMS['lba_arg']:SYMS['lba_arg']+4], 'big') lba = int.from_bytes(self.m.memory[SYMS['lba_arg']:SYMS['lba_arg']+4], 'big')
if hook == 'sdwrite_lba': self.io.append((hook, lba))
failed = lba in (self.fail_reads if hook.startswith('sdread') else self.fail_writes)
if failed:
pass
elif hook == 'sdwrite_lba_from':
source = self.get('dma_xfer_page', 1)*16384 + (self.get('dma_xfer_off') & 16383)
self.disk[lba] = self.ram[source:source+512]
elif hook == 'sdwrite_lba':
p = SYMS['sdbuf'] p = SYMS['sdbuf']
self.disk[lba] = bytearray(self.m.memory[p:p+512]) self.disk[lba] = bytearray(self.m.memory[p:p+512])
elif hook == 'sdread_lba': elif hook == 'sdread_lba':
@@ -72,9 +98,11 @@ class Driver:
else: else:
dest = self.get('dma_xfer_page', 1)*16384 + (self.get('dma_xfer_off') & 16383) dest = self.get('dma_xfer_page', 1)*16384 + (self.get('dma_xfer_off') & 16383)
self.ram[dest:dest+512] = self.disk[lba] self.ram[dest:dest+512] = self.disk[lba]
self.m.af = 0x0040 # A=0, Z=1 (successful sector I/O) self.m.af = 0x0100 if failed else 0x0040
self.m.pc = int.from_bytes(self.m.memory[self.m.sp:self.m.sp+2], 'little') self.m.pc = int.from_bytes(self.m.memory[self.m.sp:self.m.sp+2], 'little')
self.m.sp += 2 self.m.sp += 2
if self.m.pc == 0xff00:
return self.m.a
raise AssertionError(f'{name}: execution did not return') raise AssertionError(f'{name}: execution did not return')
+80
View File
@@ -0,0 +1,80 @@
"""Resident routines; build resident with --sym=resident_syms_new.txt first."""
import re
from test_fat_offsets import Driver, ROOT, SYMS, BODY
SYMS.update((name, int(value, 16)) for name, value in re.findall(
r'^(\w+): EQU 0x([0-9A-Fa-f]+)$', (ROOT/'resident_syms_new.txt').read_text(), re.M))
def driver():
d = Driver()
d.m.set_memory_block(0x2000, (ROOT/'zc_sd_driver.bin').read_bytes())
return d
def test_depacker():
d = driver()
d.m.set_memory_block(0, bytes(len(BODY)))
assert d.call('drv_dos_swp') == 0
assert bytes(d.m.memory[:len(BODY)]) == BODY
assert bytes(d.m.memory[0x2000:0x2000+(ROOT/'zc_sd_driver.bin').stat().st_size]) == (ROOT/'zc_sd_driver.bin').read_bytes()
def test_free_chain():
for broken in (False, True):
d = driver()
chain = [319, 500, 320, 1023]
for i, n in enumerate(chain):
d.fat(n, chain[i+1] if i+1 < len(chain) else 0x0ffffff8)
d.cluster(chain[0])
if broken:
d.fail_reads.add(49+chain[0]//128)
assert d.call('core_free_chain') != 0
assert not [x for x in d.io if x[0].startswith('sdwrite')]
else:
assert d.call('core_free_chain') == 0
for n in chain:
sector, offset = divmod(n*4, 512)
assert d.disk[49+sector][offset:offset+4] == bytes(4)
def test_file_abi():
d = driver()
d.put('rtc_cached', 1, 1)
for s in range(49, 113):
d.disk[s] = bytearray(512)
d.fat(2, 0x0fffffff)
d.disk[1017] = bytearray(512)
d.disk[1018] = bytearray(512)
d.put('root_cluster_lo', 2)
d.put('next_search_valid', 1, 1)
d.put('next_search_lo', 319)
d.call('core_setroot')
def arg(data):
d.m.set_memory_block(0x9000, data+b'\0')
d.m.hl = 0x9000
arg(b'\0'+(1024).to_bytes(4, 'little')+b'Example long file.bin')
d.m.ix = 0xabcd
assert d.call('wrap_mkfile') == 0
assert d.m.ix == 0xabcd
arg(b'\0Example long file.bin')
assert d.call('drv_fentry') == 1
assert (d.m.de, d.m.hl) == (0, 1024)
arg(b'\0Example long file.bin')
d.m.set_memory_block(0x9200, b'Renamed long file.bin\0')
d.m.de = 0x9200
assert d.call('core_renam') == 1
arg(b'\0Renamed long file.bin')
assert d.call('drv_fentry') == 1
arg(b'\0Example long file.bin')
assert d.call('drv_fentry') == 0
arg(b'\0Renamed long file.bin')
assert d.call('core_delfl') == 1
arg(b'\0Renamed long file.bin')
assert d.call('drv_fentry') == 0
if __name__ == '__main__':
for test in (test_depacker, test_free_chain, test_file_abi):
test()
print('PASS:', test.__name__)
+8
View File
@@ -540,6 +540,8 @@ cfc_loop ld hl,(cfc_cur_hi)
ld (cur_cluster_lo),hl ld (cur_cluster_lo),hl
call get_next_cluster call get_next_cluster
ld a,(chain_ended) ld a,(chain_ended)
cp 2
jr nc,cfc_fail ; do not free a link whose FAT read failed
or a or a
jr nz,cfc_nonext jr nz,cfc_nonext
ld hl,(cur_cluster_hi) ld hl,(cur_cluster_hi)
@@ -696,6 +698,8 @@ core_delfl inc hl ; skip flag byte
ld hl,(fbn_cluster_lo) ld hl,(fbn_cluster_lo)
ld (cur_cluster_lo),hl ld (cur_cluster_lo),hl
call core_free_chain call core_free_chain
or a
jr nz,cdf_fail
ld hl,(fbn_match_run_start_lo) ld hl,(fbn_match_run_start_lo)
ld (cmdr_start_lo),hl ld (cmdr_start_lo),hl
@@ -706,10 +710,14 @@ core_delfl inc hl ; skip flag byte
ld hl,(active_dir_lo) ld hl,(active_dir_lo)
ld (cur_cluster_lo),hl ld (cur_cluster_lo),hl
call core_mark_deleted_range call core_mark_deleted_range
or a
jr nz,cdf_fail
xor a xor a
inc a ; NZ -- FILE DELETED inc a ; NZ -- FILE DELETED
ret ret
cdf_fail xor a ; Z -- deletion failed
ret
;--- core_renam: HL -> {flag(1),oldname(1-255),0}, DE -> {newname ;--- core_renam: HL -> {flag(1),oldname(1-255),0}, DE -> {newname
;--- (1-255),0} -> renames an entry in the active directory, preserving ;--- (1-255),0} -> renames an entry in the active directory, preserving
+47 -18
View File
@@ -37,7 +37,9 @@
CONF EQU #77 CONF EQU #77
DATA EQU #57 DATA EQU #57
sdinit call csh sdinit xor a
ld (sd_block_addressed),a
call csh
ld de,512+10 ld de,512+10
call cycl call cycl
@@ -134,6 +136,9 @@ sd_v2 ld de,#01aa
sd_v2_ocr_rest in a,(c) ; discard the remaining 3 OCR bytes sd_v2_ocr_rest in a,(c) ; discard the remaining 3 OCR bytes
in a,(c) in a,(c)
in a,(c) in a,(c)
ld a,(sd_block_addressed)
or a
jr z,sd_fbs ; SDv2 SDSC also requires a 512-byte block length
sdinit_ok call csh sdinit_ok call csh
xor a xor a
@@ -145,12 +150,8 @@ sdinit_fail call csh
ret ret
;--- set_lba32: HL=bits31-16, DE=bits15-0 -> stores into lba_arg (MSB ;--- set_lba32: HL=bits31-16, DE=bits15-0 -> stores into lba_arg (MSB
;--- first). Do this BEFORE calling sdread_lba, not with the LBA sitting ;--- first). Store the LBA before SD I/O: transfer routines use registers
;--- in registers across the call -- csl calls wait, which uses DE as a ;--- for command framing and DMA setup. ---
;--- busy-loop counter and never restores it, so DE (and only DE) comes
;--- back scrambled. That's exactly what broke the register-argument
;--- version earlier: H/L survived, D/E didn't. Routing the argument
;--- through memory instead sidesteps the whole problem. ---
set_lba32 ld a,h set_lba32 ld a,h
ld (lba_arg),a ld (lba_arg),a
ld a,l ld a,l
@@ -611,6 +612,7 @@ csl push bc
wait push bc wait push bc
push af push af
push de ; preserve sdinit's retry counter across csl
ld bc,DATA ld bc,DATA
ld de,60000 ld de,60000
1 in a,(c) 1 in a,(c)
@@ -620,7 +622,8 @@ wait push bc
ld a,d ld a,d
or e or e
jr nz,1b jr nz,1b
2 pop af 2 pop de
pop af
pop bc pop bc
ret ret
@@ -1332,8 +1335,8 @@ ctf_bad xor a
;--- get_next_cluster: reads the FAT32 table entry for the cluster in ;--- get_next_cluster: reads the FAT32 table entry for the cluster in
;--- (cur_cluster_hi,cur_cluster_lo) and, if it isn't an end-of-chain ;--- (cur_cluster_hi,cur_cluster_lo) and, if it isn't an end-of-chain
;--- marker, replaces cur_cluster_hi/lo with the next cluster. Sets ;--- marker, replaces cur_cluster_hi/lo with the next cluster. Sets
;--- chain_ended=1 on end-of-chain (masked entry >=0x0FFFFFF8), an ;--- chain_ended=1 on EOC (masked entry >=0x0FFFFFF8), 2 on an
;--- out-of-range cluster, or a read error, 0 otherwise. --- ;--- invalid link or read error, 0 on a valid next cluster. ---
chain_ended db 0 chain_ended db 0
nxt_lo dw 0 nxt_lo dw 0
@@ -1342,7 +1345,7 @@ nxt_hi dw 0
get_next_cluster get_next_cluster
call cluster_to_fatpos call cluster_to_fatpos
or a or a
jr nz,gnc_end jr nz,gnc_bad
ld hl,(gnc_fatsec_hi) ld hl,(gnc_fatsec_hi)
ld de,(gnc_fatsec_lo) ld de,(gnc_fatsec_lo)
@@ -1350,7 +1353,7 @@ get_next_cluster
call set_lba32 call set_lba32
call sdread_lba call sdread_lba
or a or a
jr nz,gnc_end jr nz,gnc_bad
ld hl,(gnc_inoff) ld hl,(gnc_inoff)
ld de,sdbuf ld de,sdbuf
@@ -1373,14 +1376,34 @@ get_next_cluster
cp #0f cp #0f
jr nz,gnc_have_next jr nz,gnc_have_next
ld a,l ld a,l
cp #f8 cp #ff
jr c,gnc_have_next jr c,gnc_have_next
ld hl,(nxt_lo)
ld de,#fff0
or a
sbc hl,de
jr c,gnc_have_next
ld a,l ; #0FFFFFF0..7 are reserved/bad, not EOC
cp 8
jr c,gnc_bad
gnc_end ld a,1 gnc_end ld a,1
jr gnc_stop
gnc_bad ld a,2
gnc_stop
ld (chain_ended),a ld (chain_ended),a
ret ret
gnc_have_next ld hl,(nxt_lo) gnc_have_next ld hl,(nxt_hi)
ld a,h
or l
jr nz,gnc_valid
ld hl,(nxt_lo)
ld de,2
or a
sbc hl,de
jr c,gnc_bad ; free/reserved links 0 and 1
gnc_valid ld hl,(nxt_lo)
ld (cur_cluster_lo),hl ld (cur_cluster_lo),hl
ld hl,(nxt_hi) ld hl,(nxt_hi)
ld (cur_cluster_hi),hl ld (cur_cluster_hi),hl
@@ -1598,7 +1621,7 @@ write_fsinfo_hint
ld (sdbuf+488),hl ld (sdbuf+488),hl
jr nc,wfi_skip_count jr nc,wfi_skip_count
ld hl,(sdbuf+490) ld hl,(sdbuf+490)
ld de,1 ld de,0 ; low-word subtraction already supplied the borrow
sbc hl,de sbc hl,de
ld (sdbuf+490),hl ld (sdbuf+490),hl
@@ -1970,9 +1993,9 @@ strm_calc_sector
;--- strm_advance: moves the stream position forward by one sector, ;--- strm_advance: moves the stream position forward by one sector,
;--- crossing into the next cluster (via get_next_cluster) if that was ;--- crossing into the next cluster (via get_next_cluster) if that was
;--- the last sector of the current one. Sets stream_eoc=1 if the chain ;--- the last sector of the current one. Sets stream_eoc=1 if the chain
;--- ends there. Always returns A=0 (advancing itself can't fail -- ;--- ends there, or stream_eoc=2 on a broken link / FAT read error.
;--- get_next_cluster's own failure just means stream_eoc becomes 1, ;--- Returns A=0: the sector just transferred remains valid; the NEXT
;--- which the NEXT read/write call reports). --- ;--- read/write reports the stopped stream. Only EOC=1 permits growth. ---
strm_advance ld a,(stream_sec_in_cluster) strm_advance ld a,(stream_sec_in_cluster)
inc a inc a
ld b,a ; b = candidate new sec_in_cluster ld b,a ; b = candidate new sec_in_cluster
@@ -2086,6 +2109,8 @@ stream_data_save ds 512 ; scratch: the caller's pending sector data,
stream_write_sector stream_write_sector
ld a,(stream_eoc) ld a,(stream_eoc)
cp 2
ret nc ; a broken chain must never be extended
or a or a
jr z,strm_write_have jr z,strm_write_have
@@ -2172,6 +2197,8 @@ strm_write_have call strm_calc_sector
;--- see dma_xfer_off's own comment above. --- ;--- see dma_xfer_off's own comment above. ---
stream_write_sector_from stream_write_sector_from
ld a,(stream_eoc) ld a,(stream_eoc)
cp 2
ret nc
or a or a
jr z,strm_write_have2 jr z,strm_write_have2
@@ -3081,6 +3108,8 @@ find_free_run call stream_open
ld (wne_cur_index_lo),hl ld (wne_cur_index_lo),hl
ffr_sector_loop ld a,(stream_eoc) ffr_sector_loop ld a,(stream_eoc)
cp 2
jp nc,ffr_fail
or a or a
jr z,ffr_have_sector jr z,ffr_have_sector
call ffr_grow_dir call ffr_grow_dir
Binary file not shown.