diff --git a/_sd/body_syms.inc b/_sd/body_syms.inc index 2e1de26..8883ed9 100644 --- a/_sd/body_syms.inc +++ b/_sd/body_syms.inc @@ -1,403 +1,406 @@ -body_end: EQU 0x00001FAA -cmkf_open_stream: EQU 0x00001F75 -cmkf_final: EQU 0x00001F5F -cmkf_buildlfn: EQU 0x00001F43 -cmkf_entry: EQU 0x00001F01 -cmkf_havecl: EQU 0x00001ED3 -cmkf_alloc: EQU 0x00001EBD -cmkf_havelen: EQU 0x00001EA4 -cmkf_lendone: EQU 0x00001E9B -cmkf_lenloop: EQU 0x00001E8E -core_mkfile: EQU 0x00001E73 -cmkf_len_lo: EQU 0x00001E71 -cmkf_len_hi: EQU 0x00001E6F -cmkd_final: EQU 0x00001E5A -cmkd_buildlfn: EQU 0x00001E3E -cmkd_ownentry: EQU 0x00001E0B -dotdot_name: EQU 0x00001F91 -cmkd_dot_ok: EQU 0x00001DBF -dot_name: EQU 0x00001F86 -clear_dir_entry: EQU 0x00001F9C -cmkd_dots: EQU 0x00001D6B -cmkd_zf: EQU 0x00001D55 -cmkd_have_cl: EQU 0x00001D27 -cmkd_alloc: EQU 0x00001D11 -cmkd_toolong: EQU 0x00001CF0 -cmkd_lendone: EQU 0x00001CF4 -core_mkdir: EQU 0x00001CE3 -cmk_sec_lo: EQU 0x00001CE1 -cmk_sec_hi: EQU 0x00001CDF -cmk_cluster_lo: EQU 0x00001CDD -cmk_cluster_hi: EQU 0x00001CDB -cmk_name_ptr: EQU 0x00001CD9 -sv512_nowrap: EQU 0x00001CD5 -lv512_nowrap: EQU 0x00001CA8 -lv512_advance: EQU 0x00001C92 -lv512_eoc: EQU 0x00001CB5 -lv512_done: EQU 0x00001CAC -lv512_loop: EQU 0x00001C71 -core_load512: EQU 0x00001C66 -lv512_count: EQU 0x00001C65 -lv512_off: EQU 0x00001C63 -lv512_page: EQU 0x00001C62 -sv512_advance: EQU 0x00001CBF -sv512_eoc: EQU 0x00001C58 -sv512_done: EQU 0x00001C4F -sv512_loop: EQU 0x00001C2E -core_save512: EQU 0x00001C23 -sv512_count: EQU 0x00001C22 +body_end: EQU 0x00001FE7 +cmkf_open_stream: EQU 0x00001FB2 +cmkf_final: EQU 0x00001F9C +cmkf_buildlfn: EQU 0x00001F80 +cmkf_entry: EQU 0x00001F3E +cmkf_havecl: EQU 0x00001F10 +cmkf_alloc: EQU 0x00001EFA +cmkf_havelen: EQU 0x00001EE1 +cmkf_lendone: EQU 0x00001ED8 +cmkf_lenloop: EQU 0x00001ECB +core_mkfile: EQU 0x00001EB0 +cmkf_len_lo: EQU 0x00001EAE +cmkf_len_hi: EQU 0x00001EAC +cmkd_final: EQU 0x00001E97 +cmkd_buildlfn: EQU 0x00001E7B +cmkd_ownentry: EQU 0x00001E48 +dotdot_name: EQU 0x00001FCE +cmkd_dot_ok: EQU 0x00001DFC +dot_name: EQU 0x00001FC3 +clear_dir_entry: EQU 0x00001FD9 +cmkd_dots: EQU 0x00001DA8 +cmkd_zf: EQU 0x00001D92 +cmkd_have_cl: EQU 0x00001D64 +cmkd_alloc: EQU 0x00001D4E +cmkd_toolong: EQU 0x00001D2D +cmkd_lendone: EQU 0x00001D31 +core_mkdir: EQU 0x00001D20 +cmk_sec_lo: EQU 0x00001D1E +cmk_sec_hi: EQU 0x00001D1C +cmk_cluster_lo: EQU 0x00001D1A +cmk_cluster_hi: EQU 0x00001D18 +cmk_name_ptr: EQU 0x00001D16 +sv512_nowrap: EQU 0x00001D12 +lv512_nowrap: EQU 0x00001CE5 +lv512_advance: EQU 0x00001CCF +lv512_eoc: EQU 0x00001CF2 +lv512_done: EQU 0x00001CE9 +lv512_loop: EQU 0x00001CAE +core_load512: EQU 0x00001CA3 +lv512_count: EQU 0x00001CA2 +lv512_off: EQU 0x00001CA0 +lv512_page: EQU 0x00001C9F +sv512_advance: EQU 0x00001CFC +sv512_eoc: EQU 0x00001C95 +sv512_done: EQU 0x00001C8C +sv512_loop: EQU 0x00001C6B +core_save512: EQU 0x00001C60 +sv512_count: EQU 0x00001C5F page1_port: EQU 0x000011AF -core_fentry: EQU 0x00001C0E -core_setdir: EQU 0x00001C01 -core_setroot: EQU 0x00001BF4 -active_dir_lo: EQU 0x00001BF2 -active_dir_hi: EQU 0x00001BF0 -fbn_tm_yes: EQU 0x00001BB2 -fbn_tm_short: EQU 0x00001BA0 -fbn_acc_null: EQU 0x00001B72 -fbn_acc_pad: EQU 0x00001B7D -fbn_acc_charloop: EQU 0x00001B46 -fbn_acc_mul: EQU 0x00001B36 -fbn_acc_mul_done: EQU 0x00001B3C -fbn_matched_setrun: EQU 0x00001B07 -fbn_matched_norun: EQU 0x00001B04 -fbn_accumulate_lfn: EQU 0x00001B0D -fbn_lfn_cont: EQU 0x00001ACE -fbn_next: EQU 0x00001AD9 -fbn_matched: EQU 0x00001AF2 -fbn_try_match: EQU 0x00001B80 -fbn_is_lfn: EQU 0x00001AC0 -fbn_deleted: EQU 0x00001AD3 -fbn_notfound_pop: EQU 0x00001AEF -fbn_entloop: EQU 0x00001A9F -fbn_notfound: EQU 0x00001AF0 -fbn_sector_loop: EQU 0x00001A7F -find_by_name: EQU 0x00001A6F -fbn_match_run_start_lo: EQU 0x00001A6D -fbn_match_index_lo: EQU 0x00001A6B -fbn_run_start_lo: EQU 0x00001A69 -fbn_cur_index_lo: EQU 0x00001A67 -fbn_render_buf: EQU 0x00001A5A -fbn_attr: EQU 0x00001A59 -fbn_size_lo: EQU 0x00001A57 -fbn_size_hi: EQU 0x00001A55 -fbn_cluster_hi: EQU 0x00001A51 -fbn_acc_dest: EQU 0x00001A4F -fbn_lfn_seen: EQU 0x00001A4E -fbn_lfn_checksum: EQU 0x00001A4D -fbn_lfn_buf: EQU 0x000019E4 -fbn_name_ptr: EQU 0x000019E2 -rsn_ext: EQU 0x000019D4 -rsn_done: EQU 0x000019DE -rsn_ext_prep: EQU 0x000019C9 -rsn_base_skip: EQU 0x000019C6 -rsn_base: EQU 0x000019BA -render_shortname: EQU 0x000019B7 -seci_eq: EQU 0x000019B5 -seci_ne: EQU 0x000019B2 -str_eq_ci: EQU 0x0000199F -to_upper: EQU 0x00001996 -wne_w_skip_write: EQU 0x0000198D -wne_w_notinrange: EQU 0x0000195F -wne_w_entloop: EQU 0x00001929 -wne_w_fail: EQU 0x00001993 -wne_w_continue: EQU 0x000018F4 -wne_w_done: EQU 0x000018F2 -wne_w_sector_loop: EQU 0x000018DD -write_name_entries: EQU 0x000018D4 -wne_w_this_sec_lo: EQU 0x000018D2 -wne_w_this_sec_hi: EQU 0x000018D0 -wne_w_dirty: EQU 0x000018CF -wne_w_entcount: EQU 0x000018CE -wne_w_entptr: EQU 0x000018CC -ffr_no_hi_carry: EQU 0x0000184C -ffr_free_cont: EQU 0x00001815 -ffr_advance_index: EQU 0x00001836 -ffr_free: EQU 0x000017FF -ffr_entloop: EQU 0x000017F0 -ffr_fail: EQU 0x00001859 -ffr_grow_dir: EQU 0x0000185C -ffr_have_sector: EQU 0x000017D8 -ffr_sector_loop: EQU 0x000017C9 -find_free_run: EQU 0x000017B9 -wne_free_index_lo: EQU 0x000017B7 -wne_free_index_hi: EQU 0x000017B5 -cmkd_lenloop: EQU 0x00001CE8 -wne_cur_index_lo: EQU 0x000017B3 -wne_cur_index_hi: EQU 0x000017B1 -wne_run_start_lo: EQU 0x000017AF -wne_run_start_hi: EQU 0x000017AD -wne_run_len: EQU 0x000017AC -ble_char_nextk: EQU 0x00001779 -ble_char_hi0: EQU 0x00001777 -ble_char_have: EQU 0x0000175D -ble_char_isreal: EQU 0x00001753 -ble_char_isnull: EQU 0x0000175C -ble_lfn_next: EQU 0x0000177C -ble_charloop: EQU 0x00001734 -ble_mul13done: EQU 0x0000172F -ble_mul13: EQU 0x00001724 -ble_seqset: EQU 0x00001706 -ble_notlast: EQU 0x00001705 -ble_lfn_done: EQU 0x00001783 -ble_lfn_loop: EQU 0x000016CE -ble_divdone: EQU 0x000016B7 -ble_divloop: EQU 0x000016AC -ble_multi: EQU 0x000016A3 -build_lfn_entries: EQU 0x0000167E -ble_entry_ptr: EQU 0x0000167C -ble_charpos: EQU 0x0000167A -ble_j: EQU 0x00001679 -ble_checksum: EQU 0x00001678 -ble_lfn_count: EQU 0x00001677 -ble_name_ptr: EQU 0x00001675 -lfn_char_dest: EQU 0x00001668 -wne_slot_count: EQU 0x00001667 -wne_entries_buf: EQU 0x00001547 +core_fentry: EQU 0x00001C4B +core_setdir: EQU 0x00001C3E +core_setroot: EQU 0x00001C31 +active_dir_lo: EQU 0x00001C2F +active_dir_hi: EQU 0x00001C2D +fbn_tm_yes: EQU 0x00001BEF +fbn_tm_short: EQU 0x00001BDD +fbn_acc_null: EQU 0x00001BAF +fbn_acc_pad: EQU 0x00001BBA +fbn_acc_charloop: EQU 0x00001B83 +fbn_acc_mul: EQU 0x00001B73 +fbn_acc_mul_done: EQU 0x00001B79 +fbn_matched_setrun: EQU 0x00001B44 +fbn_matched_norun: EQU 0x00001B41 +fbn_accumulate_lfn: EQU 0x00001B4A +fbn_lfn_cont: EQU 0x00001B0B +fbn_next: EQU 0x00001B16 +fbn_matched: EQU 0x00001B2F +fbn_try_match: EQU 0x00001BBD +fbn_is_lfn: EQU 0x00001AFD +fbn_deleted: EQU 0x00001B10 +fbn_notfound_pop: EQU 0x00001B2C +fbn_entloop: EQU 0x00001ADC +fbn_notfound: EQU 0x00001B2D +fbn_sector_loop: EQU 0x00001ABC +find_by_name: EQU 0x00001AAC +fbn_match_run_start_lo: EQU 0x00001AAA +fbn_match_index_lo: EQU 0x00001AA8 +fbn_run_start_lo: EQU 0x00001AA6 +fbn_cur_index_lo: EQU 0x00001AA4 +fbn_render_buf: EQU 0x00001A97 +fbn_attr: EQU 0x00001A96 +fbn_size_lo: EQU 0x00001A94 +fbn_size_hi: EQU 0x00001A92 +fbn_cluster_hi: EQU 0x00001A8E +fbn_acc_dest: EQU 0x00001A8C +fbn_lfn_seen: EQU 0x00001A8B +fbn_lfn_checksum: EQU 0x00001A8A +fbn_lfn_buf: EQU 0x00001A21 +fbn_name_ptr: EQU 0x00001A1F +rsn_ext: EQU 0x00001A11 +rsn_done: EQU 0x00001A1B +rsn_ext_prep: EQU 0x00001A06 +rsn_base_skip: EQU 0x00001A03 +rsn_base: EQU 0x000019F7 +render_shortname: EQU 0x000019F4 +seci_eq: EQU 0x000019F2 +seci_ne: EQU 0x000019EF +str_eq_ci: EQU 0x000019DC +to_upper: EQU 0x000019D3 +wne_w_skip_write: EQU 0x000019CA +wne_w_notinrange: EQU 0x0000199C +wne_w_entloop: EQU 0x00001966 +wne_w_fail: EQU 0x000019D0 +wne_w_continue: EQU 0x00001931 +wne_w_done: EQU 0x0000192F +wne_w_sector_loop: EQU 0x0000191A +write_name_entries: EQU 0x00001911 +wne_w_this_sec_lo: EQU 0x0000190F +wne_w_this_sec_hi: EQU 0x0000190D +wne_w_dirty: EQU 0x0000190C +wne_w_entcount: EQU 0x0000190B +wne_w_entptr: EQU 0x00001909 +ffr_no_hi_carry: EQU 0x00001889 +ffr_free_cont: EQU 0x00001852 +ffr_advance_index: EQU 0x00001873 +ffr_free: EQU 0x0000183C +ffr_entloop: EQU 0x0000182D +ffr_grow_dir: EQU 0x00001899 +ffr_have_sector: EQU 0x00001815 +ffr_fail: EQU 0x00001896 +ffr_sector_loop: EQU 0x00001801 +find_free_run: EQU 0x000017F1 +wne_free_index_lo: EQU 0x000017EF +wne_free_index_hi: EQU 0x000017ED +cmkd_lenloop: EQU 0x00001D25 +wne_cur_index_lo: EQU 0x000017EB +wne_cur_index_hi: EQU 0x000017E9 +wne_run_start_lo: EQU 0x000017E7 +wne_run_start_hi: EQU 0x000017E5 +wne_run_len: EQU 0x000017E4 +ble_char_nextk: EQU 0x000017B1 +ble_char_hi0: EQU 0x000017AF +ble_char_have: EQU 0x00001795 +ble_char_isreal: EQU 0x0000178B +ble_char_isnull: EQU 0x00001794 +ble_lfn_next: EQU 0x000017B4 +ble_charloop: EQU 0x0000176C +ble_mul13done: EQU 0x00001767 +ble_mul13: EQU 0x0000175C +ble_seqset: EQU 0x0000173E +ble_notlast: EQU 0x0000173D +ble_lfn_done: EQU 0x000017BB +ble_lfn_loop: EQU 0x00001706 +ble_divdone: EQU 0x000016EF +ble_divloop: EQU 0x000016E4 +ble_multi: EQU 0x000016DB +build_lfn_entries: EQU 0x000016B6 +ble_entry_ptr: EQU 0x000016B4 +ble_charpos: EQU 0x000016B2 +ble_j: EQU 0x000016B1 +ble_checksum: EQU 0x000016B0 +ble_lfn_count: EQU 0x000016AF +ble_name_ptr: EQU 0x000016AD +lfn_char_dest: EQU 0x000016A0 +wne_slot_count: EQU 0x0000169F +wne_entries_buf: EQU 0x0000157F wne_max_lfn: EQU 0x00000008 -btd_done: EQU 0x0000153B -btd_loop: EQU 0x00001532 -gus_have_candidate: EQU 0x00001511 -bin_to_2dig: EQU 0x00001530 -gus_try_1digit: EQU 0x000014F9 -gus_try: EQU 0x000014D4 -gus_ok: EQU 0x0000152E -gus_needs_suffix: EQU 0x000014CF -generate_unique_shortname: EQU 0x000014B0 -gus_suffix: EQU 0x000014AF -fn83_ext_full: EQU 0x000014A1 -fn83_ext_bad: EQU 0x000014A8 -fn83_ext_loop: EQU 0x00001483 -fn83_ext_go: EQU 0x0000146C -fn83_base_full: EQU 0x00001451 -fn83_base_bad: EQU 0x00001458 -fn83_ext: EQU 0x0000145F -fn83_base_loop: EQU 0x00001432 -fn83_have_baselen: EQU 0x00001427 -fn83_scan_next: EQU 0x000013E5 -fn83_scandone: EQU 0x000013E9 -fn83_scan: EQU 0x000013D9 -filter_name_to_83: EQU 0x000013CE -fn83_charbuf: EQU 0x000013CD -sn_base_len: EQU 0x000013CB -sn_dot_pos: EQU 0x000013C9 -sn_name_len: EQU 0x000013C7 -sn_needs_lfn: EQU 0x000013C6 -sn_ext: EQU 0x000013C3 -sn_base: EQU 0x000013BB -cc_symyes: EQU 0x000013B6 -cc_no: EQU 0x000013B9 -cc_symloop: EQU 0x000013AC -cc_sym: EQU 0x000013A8 -cc_yes: EQU 0x000013B7 -cc_dig: EQU 0x000013A0 -cc_up: EQU 0x00001398 -classify_char: EQU 0x0000138E -cc_symbols: EQU 0x0000137D -lfnck_loop: EQU 0x00001377 -lfn_checksum: EQU 0x00001374 -stamp_dir_entry_datetime: EQU 0x0000115D -rtc_to_fat_datetime: EQU 0x000010E1 -rtc_cached: EQU 0x000010E0 -fat_time: EQU 0x000010DE -fat_date: EQU 0x000010DC -bcd_to_bin: EQU 0x000010C7 -rtc_field_to_bin: EQU 0x000010BB -rtc_dm: EQU 0x000010BA -rtc_year: EQU 0x00001049 -rtc_month: EQU 0x00001048 -rtc_date: EQU 0x00001047 -rtc_hour: EQU 0x00001046 -rtc_min: EQU 0x00001045 -rtc_sec: EQU 0x00001044 -sv512_off: EQU 0x00001C20 -sv512_page: EQU 0x00001C1F -strm_write_have2: EQU 0x0000102F -stream_write_sector_from: EQU 0x00000FCB -read_rtc_datetime: EQU 0x0000104A -strm_write_have: EQU 0x00000FB6 -stream_write_sector: EQU 0x00000F48 -stream_read_sector_to: EQU 0x00000D2D -stream_data_save: EQU 0x00000D48 -strm_read_eoc: EQU 0x00000D2A -stream_read_sector: EQU 0x00000CF7 -strm_adv_same: EQU 0x00000CF1 -strm_adv_cross: EQU 0x00000CCA -strm_adv_test_z: EQU 0x00000CC2 -strm_advance: EQU 0x00000CB5 -strm_calc_sector: EQU 0x00000C8D -strm_open_nz: EQU 0x00000C79 -stream_open: EQU 0x00000C5F -strm_sector_lo: EQU 0x00000C5D -strm_sector_hi: EQU 0x00000C5B -stream_eoc: EQU 0x00000C5A -stream_sec_in_cluster: EQU 0x00000C59 -stream_cluster_lo: EQU 0x00000C57 -stream_cluster_hi: EQU 0x00000C55 -wde_next: EQU 0x00000C24 -wde_slot: EQU 0x00000C35 -wde_entloop: EQU 0x00000C04 -wde_fail: EQU 0x00000C52 -wde_sec_loop: EQU 0x00000BEF -wde_cluster: EQU 0x00000BDA -write_dir_entry: EQU 0x00000BDA -wde_slotptr: EQU 0x00000BD8 -wde_sector_lo: EQU 0x00000BD6 -wde_sector_hi: EQU 0x00000BD4 -wde_remaining: EQU 0x00000BD3 -dir_entry_buf: EQU 0x00000BB3 -zfc_loop: EQU 0x00000BA1 -zfc_fail: EQU 0x00000BB0 -zero_fill_cluster: EQU 0x00000B7B -zfc_remaining: EQU 0x00000B7A -ffc_nowrap: EQU 0x00000B6B -ffc_nsv2: EQU 0x00000B45 -ffc_nsv: EQU 0x00000B3F -ffc_entnext: EQU 0x00000B4F -ffc_entloop: EQU 0x00000B08 -ffc_fail: EQU 0x00000B77 -ffc_from2: EQU 0x00000AC1 -ffc_scan: EQU 0x00000AD0 -ffc_use_hint: EQU 0x00000AA7 -find_free_cluster: EQU 0x00000A8C -next_search_valid: EQU 0x00000A87 -ffc_sectors_read: EQU 0x00000A85 -ffc_count: EQU 0x00000A84 -ffc_ptr: EQU 0x00000A82 -free_cluster_lo: EQU 0x00000A80 -free_cluster_hi: EQU 0x00000A7E -next_search_hi: EQU 0x00000A88 -next_search_lo: EQU 0x00000A8A -wfi_skip_count: EQU 0x00000A62 -write_fsinfo_hint: EQU 0x00000A27 -read_fsinfo_hint: EQU 0x000009C8 -fsinfo_valid: EQU 0x000009C7 -fsinfo_hint_lo: EQU 0x000009C5 -fsinfo_hint_hi: EQU 0x000009C3 -wfe_loop: EQU 0x0000098C -wfe_fail: EQU 0x000009BE -write_fat_entry: EQU 0x00000940 -wfe_sector_lo: EQU 0x0000093E -wfe_sector_hi: EQU 0x0000093C -wfe_remaining: EQU 0x0000093B -new_val_lo: EQU 0x00000939 -new_val_hi: EQU 0x00000937 -gnc_have_next: EQU 0x00000926 -gnc_end: EQU 0x00000920 -nxt_hi: EQU 0x000008DC -nxt_lo: EQU 0x000008DA -ctf_bad: EQU 0x000008D6 -ctf_shr: EQU 0x00000884 -cluster_to_fatpos: EQU 0x00000876 -gnc_inoff: EQU 0x00000874 -gnc_fatsec_lo: EQU 0x00000872 -gnc_fatsec_hi: EQU 0x00000870 -gnc_lo: EQU 0x0000086E -gnc_hi: EQU 0x0000086C -c2s_loop: EQU 0x00000832 -c2s_done: EQU 0x0000084B -cur_cluster_hi: EQU 0x00000868 -cur_cluster_lo: EQU 0x0000086A -c2s_prod_hi: EQU 0x00000805 -c2s_prod_lo: EQU 0x00000803 -c2s_hi: EQU 0x00000801 -c2s_lo: EQU 0x000007FF -chain_ended: EQU 0x000008D9 -get_next_cluster: EQU 0x000008DE -fza_next: EQU 0x000007E9 -fza_inc_loop: EQU 0x000007E1 -fza_done: EQU 0x000007FC -fza_stop: EQU 0x000007FA -fza_loop: EQU 0x000007CB -fza_sector_lo: EQU 0x00000807 -fza_sector_hi: EQU 0x00000809 -cluster_to_sector: EQU 0x0000080B -fza_cluster: EQU 0x000007B8 -find_zifi_all: EQU 0x000007B8 -fza_remaining: EQU 0x000007B7 -fz_found: EQU 0x00000782 -fz_cmpfail: EQU 0x00000775 -fz_cmp: EQU 0x00000769 -fz_skip: EQU 0x00000777 -fz_end: EQU 0x0000077F -fz_loop: EQU 0x0000075A -find_zifi: EQU 0x00000755 -target_name: EQU 0x0000074A -zifi_size: EQU 0x00000748 -fbn_acc_nocs: EQU 0x00001B21 -zifi_size_hi: EQU 0x00000746 -zifi_cluster: EQU 0x00000744 -zifi_cluster_hi: EQU 0x00000742 -root_dir_hi: EQU 0x00000740 -root_dir_lo: EQU 0x0000073E -mul2_loop: EQU 0x000006D2 -mul2_done: EQU 0x000006EB -prod2_hi: EQU 0x0000073C -prod2_lo: EQU 0x0000073A -rc2_hi: EQU 0x00000738 -rc2_lo: EQU 0x00000736 -data_start_hi: EQU 0x00000734 -data_start_lo: EQU 0x00000732 -mul1_loop: EQU 0x0000067B -mul1_done: EQU 0x00000694 -prod_hi: EQU 0x00000730 -prod_lo: EQU 0x0000072E -root_cluster_lo: EQU 0x0000072A -root_cluster_hi: EQU 0x0000072C -fatsz32_lo: EQU 0x00000726 -fatsz32_hi: EQU 0x00000728 -sectors_per_cluster: EQU 0x00000725 -num_fats: EQU 0x00000724 -fsinfo_sector: EQU 0x000009C1 -reserved_sectors: EQU 0x00000722 -total_sectors_hi: EQU 0x0000071C -total_sectors_lo: EQU 0x0000071A -parse_bpb: EQU 0x0000062F -vf_ok: EQU 0x0000062A -vf_bad: EQU 0x0000062C -verify_fat32: EQU 0x000005EA -dp_fallback: EQU 0x000005CE -dp_found: EQU 0x0000059F -dp_entry: EQU 0x000005E8 -dp_loop: EQU 0x00000585 -dp_none: EQU 0x0000059D -detect_partition: EQU 0x0000055A -sec0_copy: EQU 0x0000035A -add_addtop: EQU 0x0000034A -addtop_lo: EQU 0x00000348 -addtop_hi: EQU 0x00000346 -cmdx: EQU 0x00000313 -do_cmd55: EQU 0x000002ED -cmdo: EQU 0x0000030D -send_frame: EQU 0x000002F5 -cmd16: EQU 0x000002C5 -cmd08: EQU 0x000002BF -cmd00: EQU 0x000002B9 -sdwrite_lba_from: EQU 0x00000234 -sdwrite_lba: EQU 0x00000231 -sdwrite_multi_stop: EQU 0x0000021C -sdwrite_multi_block_from: EQU 0x000001F7 -sdwrite_multi_block: EQU 0x000001F4 -csl: EQU 0x00000258 -wait: EQU 0x0000026D -sdwrite_multi_start: EQU 0x000001AF -wtdo: EQU 0x00000282 -sdread_fail: EQU 0x000001A6 -resp: EQU 0x00000298 -sdread_common: EQU 0x0000018D -sdread_lba_oob: EQU 0x000001AC -send_cmd_lba: EQU 0x000001C7 -cmdlba_op: EQU 0x000001C6 -sdread_lba_to: EQU 0x00000182 -sdread_lba: EQU 0x0000017F -dma_send_ext: EQU 0x0000015F -dma_wait: EQU 0x00000179 -dma_recv_ext: EQU 0x00000143 -dma_set_addr_ext: EQU 0x0000012D -sdbuf: EQU 0x00001174 -set_dma_sdbuf: EQU 0x00000121 -dma_xfer_page: EQU 0x00000120 -dma_xfer_off: EQU 0x0000011E +btd_done: EQU 0x00001573 +btd_loop: EQU 0x0000156A +gus_have_candidate: EQU 0x00001549 +bin_to_2dig: EQU 0x00001568 +gus_try_1digit: EQU 0x00001531 +gus_try: EQU 0x0000150C +gus_ok: EQU 0x00001566 +gus_needs_suffix: EQU 0x00001507 +generate_unique_shortname: EQU 0x000014E8 +gus_suffix: EQU 0x000014E7 +fn83_ext_full: EQU 0x000014D9 +fn83_ext_bad: EQU 0x000014E0 +fn83_ext_loop: EQU 0x000014BB +fn83_ext_go: EQU 0x000014A4 +fn83_base_full: EQU 0x00001489 +fn83_base_bad: EQU 0x00001490 +fn83_ext: EQU 0x00001497 +fn83_base_loop: EQU 0x0000146A +fn83_have_baselen: EQU 0x0000145F +fn83_scan_next: EQU 0x0000141D +fn83_scandone: EQU 0x00001421 +fn83_scan: EQU 0x00001411 +filter_name_to_83: EQU 0x00001406 +fn83_charbuf: EQU 0x00001405 +sn_base_len: EQU 0x00001403 +sn_dot_pos: EQU 0x00001401 +sn_name_len: EQU 0x000013FF +sn_needs_lfn: EQU 0x000013FE +sn_ext: EQU 0x000013FB +sn_base: EQU 0x000013F3 +cc_symyes: EQU 0x000013EE +cc_no: EQU 0x000013F1 +cc_symloop: EQU 0x000013E4 +cc_sym: EQU 0x000013E0 +cc_yes: EQU 0x000013EF +cc_dig: EQU 0x000013D8 +cc_up: EQU 0x000013D0 +classify_char: EQU 0x000013C6 +cc_symbols: EQU 0x000013B5 +lfnck_loop: EQU 0x000013AF +lfn_checksum: EQU 0x000013AC +stamp_dir_entry_datetime: EQU 0x00001195 +rtc_to_fat_datetime: EQU 0x00001119 +rtc_cached: EQU 0x00001118 +fat_time: EQU 0x00001116 +fat_date: EQU 0x00001114 +bcd_to_bin: EQU 0x000010FF +rtc_field_to_bin: EQU 0x000010F3 +rtc_dm: EQU 0x000010F2 +rtc_year: EQU 0x00001081 +rtc_month: EQU 0x00001080 +rtc_date: EQU 0x0000107F +rtc_hour: EQU 0x0000107E +rtc_min: EQU 0x0000107D +rtc_sec: EQU 0x0000107C +sv512_off: EQU 0x00001C5D +sv512_page: EQU 0x00001C5C +strm_write_have2: EQU 0x00001067 +stream_write_sector_from: EQU 0x00001000 +read_rtc_datetime: EQU 0x00001082 +strm_write_have: EQU 0x00000FEB +stream_write_sector: EQU 0x00000F7A +stream_read_sector_to: EQU 0x00000D5F +stream_data_save: EQU 0x00000D7A +strm_read_eoc: EQU 0x00000D5C +stream_read_sector: EQU 0x00000D29 +strm_adv_same: EQU 0x00000D23 +strm_adv_cross: EQU 0x00000CFC +strm_adv_test_z: EQU 0x00000CF4 +strm_advance: EQU 0x00000CE7 +strm_calc_sector: EQU 0x00000CBF +strm_open_nz: EQU 0x00000CAB +stream_open: EQU 0x00000C91 +strm_sector_lo: EQU 0x00000C8F +strm_sector_hi: EQU 0x00000C8D +stream_eoc: EQU 0x00000C8C +stream_sec_in_cluster: EQU 0x00000C8B +stream_cluster_lo: EQU 0x00000C89 +stream_cluster_hi: EQU 0x00000C87 +wde_next: EQU 0x00000C56 +wde_slot: EQU 0x00000C67 +wde_entloop: EQU 0x00000C36 +wde_fail: EQU 0x00000C84 +wde_sec_loop: EQU 0x00000C21 +wde_cluster: EQU 0x00000C0C +write_dir_entry: EQU 0x00000C0C +wde_slotptr: EQU 0x00000C0A +wde_sector_lo: EQU 0x00000C08 +wde_sector_hi: EQU 0x00000C06 +wde_remaining: EQU 0x00000C05 +dir_entry_buf: EQU 0x00000BE5 +zfc_loop: EQU 0x00000BD3 +zfc_fail: EQU 0x00000BE2 +zero_fill_cluster: EQU 0x00000BAD +zfc_remaining: EQU 0x00000BAC +ffc_nowrap: EQU 0x00000B9D +ffc_nsv2: EQU 0x00000B77 +ffc_nsv: EQU 0x00000B71 +ffc_entnext: EQU 0x00000B81 +ffc_entloop: EQU 0x00000B3A +ffc_fail: EQU 0x00000BA9 +ffc_from2: EQU 0x00000AF3 +ffc_scan: EQU 0x00000B02 +ffc_use_hint: EQU 0x00000AD9 +find_free_cluster: EQU 0x00000ABE +next_search_valid: EQU 0x00000AB9 +ffc_sectors_read: EQU 0x00000AB7 +ffc_count: EQU 0x00000AB6 +ffc_ptr: EQU 0x00000AB4 +free_cluster_lo: EQU 0x00000AB2 +free_cluster_hi: EQU 0x00000AB0 +next_search_hi: EQU 0x00000ABA +next_search_lo: EQU 0x00000ABC +wfi_skip_count: EQU 0x00000A94 +write_fsinfo_hint: EQU 0x00000A59 +read_fsinfo_hint: EQU 0x000009FA +fsinfo_valid: EQU 0x000009F9 +fsinfo_hint_lo: EQU 0x000009F7 +fsinfo_hint_hi: EQU 0x000009F5 +wfe_loop: EQU 0x000009BE +wfe_fail: EQU 0x000009F0 +write_fat_entry: EQU 0x00000972 +wfe_sector_lo: EQU 0x00000970 +wfe_sector_hi: EQU 0x0000096E +wfe_remaining: EQU 0x0000096D +new_val_lo: EQU 0x0000096B +new_val_hi: EQU 0x00000969 +gnc_valid: EQU 0x00000958 +gnc_stop: EQU 0x00000942 +gnc_end: EQU 0x0000093C +gnc_have_next: EQU 0x00000946 +gnc_bad: EQU 0x00000940 +nxt_hi: EQU 0x000008E8 +nxt_lo: EQU 0x000008E6 +ctf_bad: EQU 0x000008E2 +ctf_shr: EQU 0x00000890 +cluster_to_fatpos: EQU 0x00000882 +gnc_inoff: EQU 0x00000880 +gnc_fatsec_lo: EQU 0x0000087E +gnc_fatsec_hi: EQU 0x0000087C +gnc_lo: EQU 0x0000087A +gnc_hi: EQU 0x00000878 +c2s_loop: EQU 0x0000083E +c2s_done: EQU 0x00000857 +cur_cluster_hi: EQU 0x00000874 +cur_cluster_lo: EQU 0x00000876 +c2s_prod_hi: EQU 0x00000811 +c2s_prod_lo: EQU 0x0000080F +c2s_hi: EQU 0x0000080D +c2s_lo: EQU 0x0000080B +chain_ended: EQU 0x000008E5 +get_next_cluster: EQU 0x000008EA +fza_next: EQU 0x000007F5 +fza_inc_loop: EQU 0x000007ED +fza_done: EQU 0x00000808 +fza_stop: EQU 0x00000806 +fza_loop: EQU 0x000007D7 +fza_sector_lo: EQU 0x00000813 +fza_sector_hi: EQU 0x00000815 +cluster_to_sector: EQU 0x00000817 +fza_cluster: EQU 0x000007C4 +find_zifi_all: EQU 0x000007C4 +fza_remaining: EQU 0x000007C3 +fz_found: EQU 0x0000078E +fz_cmpfail: EQU 0x00000781 +fz_cmp: EQU 0x00000775 +fz_skip: EQU 0x00000783 +fz_end: EQU 0x0000078B +fz_loop: EQU 0x00000766 +find_zifi: EQU 0x00000761 +target_name: EQU 0x00000756 +zifi_size: EQU 0x00000754 +fbn_acc_nocs: EQU 0x00001B5E +zifi_size_hi: EQU 0x00000752 +zifi_cluster: EQU 0x00000750 +zifi_cluster_hi: EQU 0x0000074E +root_dir_hi: EQU 0x0000074C +root_dir_lo: EQU 0x0000074A +mul2_loop: EQU 0x000006DE +mul2_done: EQU 0x000006F7 +prod2_hi: EQU 0x00000748 +prod2_lo: EQU 0x00000746 +rc2_hi: EQU 0x00000744 +rc2_lo: EQU 0x00000742 +data_start_hi: EQU 0x00000740 +data_start_lo: EQU 0x0000073E +mul1_loop: EQU 0x00000687 +mul1_done: EQU 0x000006A0 +prod_hi: EQU 0x0000073C +prod_lo: EQU 0x0000073A +root_cluster_lo: EQU 0x00000736 +root_cluster_hi: EQU 0x00000738 +fatsz32_lo: EQU 0x00000732 +fatsz32_hi: EQU 0x00000734 +sectors_per_cluster: EQU 0x00000731 +num_fats: EQU 0x00000730 +fsinfo_sector: EQU 0x000009F3 +reserved_sectors: EQU 0x0000072E +total_sectors_hi: EQU 0x00000728 +total_sectors_lo: EQU 0x00000726 +parse_bpb: EQU 0x0000063B +vf_ok: EQU 0x00000636 +vf_bad: EQU 0x00000638 +verify_fat32: EQU 0x000005F6 +dp_fallback: EQU 0x000005DA +dp_found: EQU 0x000005AB +dp_entry: EQU 0x000005F4 +dp_loop: EQU 0x00000591 +dp_none: EQU 0x000005A9 +detect_partition: EQU 0x00000566 +sec0_copy: EQU 0x00000366 +add_addtop: EQU 0x00000356 +addtop_lo: EQU 0x00000354 +addtop_hi: EQU 0x00000352 +cmdx: EQU 0x0000031F +do_cmd55: EQU 0x000002F9 +cmdo: EQU 0x00000319 +send_frame: EQU 0x00000301 +cmd16: EQU 0x000002D1 +cmd08: EQU 0x000002CB +cmd00: EQU 0x000002C5 +sdwrite_lba_from: EQU 0x0000023E +sdwrite_lba: EQU 0x0000023B +sdwrite_multi_stop: EQU 0x00000226 +sdwrite_multi_block_from: EQU 0x00000201 +sdwrite_multi_block: EQU 0x000001FE +csl: EQU 0x00000262 +wait: EQU 0x00000277 +sdwrite_multi_start: EQU 0x000001B9 +wtdo: EQU 0x0000028E +sdread_fail: EQU 0x000001B0 +resp: EQU 0x000002A4 +sdread_common: EQU 0x00000197 +sdread_lba_oob: EQU 0x000001B6 +send_cmd_lba: EQU 0x000001D1 +cmdlba_op: EQU 0x000001D0 +sdread_lba_to: EQU 0x0000018C +sdread_lba: EQU 0x00000189 +dma_send_ext: EQU 0x00000169 +dma_wait: EQU 0x00000183 +dma_recv_ext: EQU 0x0000014D +dma_set_addr_ext: EQU 0x00000137 +sdbuf: EQU 0x000011AC +set_dma_sdbuf: EQU 0x0000012B +dma_xfer_page: EQU 0x0000012A +dma_xfer_off: EQU 0x00000128 dma_ctr: EQU 0x000027AF dma_num: EQU 0x000028AF dma_len: EQU 0x000026AF @@ -408,27 +411,27 @@ dma_daddrx: EQU 0x00001FAF dma_daddrh: EQU 0x00001EAF dma_daddrl: EQU 0x00001DAF own_page: EQU 0x0000000F -cca_shift: EQU 0x000000F4 -compute_card_arg: EQU 0x000000E2 -card_arg: EQU 0x000000DE -sd_lba_max_hi: EQU 0x00000720 -sd_lba_max_lo: EQU 0x0000071E -lba_guard_check: EQU 0x000000BF -lba_arg: EQU 0x000000BB -set_lba32: EQU 0x000000AA -sd_block_addressed: EQU 0x000000DD -sd_v2_ocr_rest: EQU 0x00000099 -do_cmd58: EQU 0x000002DD -sdinit_ok: EQU 0x0000009F -do_cmd16: EQU 0x000002D7 -do_cmd1: EQU 0x000002E5 -sd_fbs: EQU 0x0000005D -do_acmd41: EQU 0x00000326 -sd_v2: EQU 0x00000067 -do_cmd8: EQU 0x000002D1 -do_cmd0: EQU 0x000002CB -sdinit_fail: EQU 0x000000A4 -cycl: EQU 0x000002AC -csh: EQU 0x00000245 +cca_shift: EQU 0x000000FE +compute_card_arg: EQU 0x000000EC +card_arg: EQU 0x000000E8 +sd_lba_max_hi: EQU 0x0000072C +sd_lba_max_lo: EQU 0x0000072A +lba_guard_check: EQU 0x000000C9 +lba_arg: EQU 0x000000C5 +set_lba32: EQU 0x000000B4 +sd_v2_ocr_rest: EQU 0x0000009D +do_cmd58: EQU 0x000002E9 +sdinit_ok: EQU 0x000000A9 +do_cmd16: EQU 0x000002E3 +do_cmd1: EQU 0x000002F1 +sd_fbs: EQU 0x00000061 +do_acmd41: EQU 0x00000332 +sd_v2: EQU 0x0000006B +do_cmd8: EQU 0x000002DD +do_cmd0: EQU 0x000002D7 +sdinit_fail: EQU 0x000000AE +cycl: EQU 0x000002B8 +csh: EQU 0x0000024F +sd_block_addressed: EQU 0x000000E7 sdinit: EQU 0x00000000 -fbn_cluster_lo: EQU 0x00001A53 +fbn_cluster_lo: EQU 0x00001A90 diff --git a/_sd/tests/README.md b/_sd/tests/README.md new file mode 100644 index 0000000..653494e --- /dev/null +++ b/_sd/tests/README.md @@ -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 или защиту от всех вариантов повреждённого тома. diff --git a/_sd/tests/test_driver.py b/_sd/tests/test_driver.py new file mode 100644 index 0000000..e12a3be --- /dev/null +++ b/_sd/tests/test_driver.py @@ -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') diff --git a/_sd/tests/test_fat_offsets.py b/_sd/tests/test_fat_offsets.py index 6bf9bf3..9380cd4 100644 --- a/_sd/tests/test_fat_offsets.py +++ b/_sd/tests/test_fat_offsets.py @@ -20,9 +20,16 @@ class Driver: self.m = z80.Z80Machine() self.m.set_memory_block(0, BODY) self.disk = {} + self.fail_reads = set() + self.fail_writes = set() + self.io = [] self.ram = bytearray(256 * 16384) 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]: self.m.set_breakpoint(address) self.put('reserved_sectors', 32) @@ -59,9 +66,28 @@ class Driver: hook = self.hooks.get(self.m.pc) if not hook: 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') - 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'] self.disk[lba] = bytearray(self.m.memory[p:p+512]) elif hook == 'sdread_lba': @@ -72,9 +98,11 @@ class Driver: else: dest = self.get('dma_xfer_page', 1)*16384 + (self.get('dma_xfer_off') & 16383) 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.sp += 2 + if self.m.pc == 0xff00: + return self.m.a raise AssertionError(f'{name}: execution did not return') diff --git a/_sd/tests/test_resident.py b/_sd/tests/test_resident.py new file mode 100644 index 0000000..98d3cf7 --- /dev/null +++ b/_sd/tests/test_resident.py @@ -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__) diff --git a/_sd/zc_sd_driver.asm b/_sd/zc_sd_driver.asm index bc0108a..f36fe4c 100644 --- a/_sd/zc_sd_driver.asm +++ b/_sd/zc_sd_driver.asm @@ -540,6 +540,8 @@ cfc_loop ld hl,(cfc_cur_hi) ld (cur_cluster_lo),hl call get_next_cluster ld a,(chain_ended) + cp 2 + jr nc,cfc_fail ; do not free a link whose FAT read failed or a jr nz,cfc_nonext ld hl,(cur_cluster_hi) @@ -696,6 +698,8 @@ core_delfl inc hl ; skip flag byte ld hl,(fbn_cluster_lo) ld (cur_cluster_lo),hl call core_free_chain + or a + jr nz,cdf_fail ld hl,(fbn_match_run_start_lo) ld (cmdr_start_lo),hl @@ -706,10 +710,14 @@ core_delfl inc hl ; skip flag byte ld hl,(active_dir_lo) ld (cur_cluster_lo),hl call core_mark_deleted_range + or a + jr nz,cdf_fail xor a inc a ; NZ -- FILE DELETED ret +cdf_fail xor a ; Z -- deletion failed + ret ;--- core_renam: HL -> {flag(1),oldname(1-255),0}, DE -> {newname ;--- (1-255),0} -> renames an entry in the active directory, preserving diff --git a/_sd/zc_sd_driver_body.asm b/_sd/zc_sd_driver_body.asm index 61f862a..ba2fbd3 100644 --- a/_sd/zc_sd_driver_body.asm +++ b/_sd/zc_sd_driver_body.asm @@ -37,7 +37,9 @@ CONF EQU #77 DATA EQU #57 -sdinit call csh +sdinit xor a + ld (sd_block_addressed),a + call csh ld de,512+10 call cycl @@ -134,6 +136,9 @@ sd_v2 ld de,#01aa sd_v2_ocr_rest in a,(c) ; discard the remaining 3 OCR bytes 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 xor a @@ -145,12 +150,8 @@ sdinit_fail call csh ret ;--- 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 -;--- in registers across the call -- csl calls wait, which uses DE as a -;--- 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. --- +;--- first). Store the LBA before SD I/O: transfer routines use registers +;--- for command framing and DMA setup. --- set_lba32 ld a,h ld (lba_arg),a ld a,l @@ -611,6 +612,7 @@ csl push bc wait push bc push af + push de ; preserve sdinit's retry counter across csl ld bc,DATA ld de,60000 1 in a,(c) @@ -620,7 +622,8 @@ wait push bc ld a,d or e jr nz,1b -2 pop af +2 pop de + pop af pop bc ret @@ -1332,8 +1335,8 @@ ctf_bad xor a ;--- 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 ;--- marker, replaces cur_cluster_hi/lo with the next cluster. Sets -;--- chain_ended=1 on end-of-chain (masked entry >=0x0FFFFFF8), an -;--- out-of-range cluster, or a read error, 0 otherwise. --- +;--- chain_ended=1 on EOC (masked entry >=0x0FFFFFF8), 2 on an +;--- invalid link or read error, 0 on a valid next cluster. --- chain_ended db 0 nxt_lo dw 0 @@ -1342,7 +1345,7 @@ nxt_hi dw 0 get_next_cluster call cluster_to_fatpos or a - jr nz,gnc_end + jr nz,gnc_bad ld hl,(gnc_fatsec_hi) ld de,(gnc_fatsec_lo) @@ -1350,7 +1353,7 @@ get_next_cluster call set_lba32 call sdread_lba or a - jr nz,gnc_end + jr nz,gnc_bad ld hl,(gnc_inoff) ld de,sdbuf @@ -1373,14 +1376,34 @@ get_next_cluster cp #0f jr nz,gnc_have_next ld a,l - cp #f8 + cp #ff 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 + jr gnc_stop +gnc_bad ld a,2 +gnc_stop ld (chain_ended),a 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 hl,(nxt_hi) ld (cur_cluster_hi),hl @@ -1598,7 +1621,7 @@ write_fsinfo_hint ld (sdbuf+488),hl jr nc,wfi_skip_count ld hl,(sdbuf+490) - ld de,1 + ld de,0 ; low-word subtraction already supplied the borrow sbc hl,de ld (sdbuf+490),hl @@ -1970,9 +1993,9 @@ strm_calc_sector ;--- strm_advance: moves the stream position forward by one sector, ;--- 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 -;--- ends there. Always returns A=0 (advancing itself can't fail -- -;--- get_next_cluster's own failure just means stream_eoc becomes 1, -;--- which the NEXT read/write call reports). --- +;--- ends there, or stream_eoc=2 on a broken link / FAT read error. +;--- Returns A=0: the sector just transferred remains valid; the NEXT +;--- read/write reports the stopped stream. Only EOC=1 permits growth. --- strm_advance ld a,(stream_sec_in_cluster) inc a 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 ld a,(stream_eoc) + cp 2 + ret nc ; a broken chain must never be extended or a jr z,strm_write_have @@ -2172,6 +2197,8 @@ strm_write_have call strm_calc_sector ;--- see dma_xfer_off's own comment above. --- stream_write_sector_from ld a,(stream_eoc) + cp 2 + ret nc or a jr z,strm_write_have2 @@ -3081,6 +3108,8 @@ find_free_run call stream_open ld (wne_cur_index_lo),hl ffr_sector_loop ld a,(stream_eoc) + cp 2 + jp nc,ffr_fail or a jr z,ffr_have_sector call ffr_grow_dir diff --git a/_spg/ZIFI/zifi.spg b/_spg/ZIFI/zifi.spg index 2f6d1a9..d73bca0 100644 Binary files a/_spg/ZIFI/zifi.spg and b/_spg/ZIFI/zifi.spg differ