; ============================================================
; HDDDUMP — SV-608M Hard Disk Dump Utility
; ============================================================
;
; Platform:  SVI-328 + SV-608M
; Format:    Standalone ROM cartridge, org 0000h
; Function:  Dumps the entire hard disk (tracks 0–1159,
;            32 sectors/track, 256 bytes/sector) to PicoExpander
;            flash via the dump disk interface.
;
; Prerequisites:
;   - PicoExpander is connected and running
;   - SV-608M controller with hard disk attached
;
; The program initialises the TMS9918 VDP for text mode
; output, then resets the SASI bus and configures the
; WD1002-SHD controller via Set Parameters (0Ch) for
; ST-212 geometry.  It does not depend on any BIOS or OS.
;
; Output: 37,120 sectors × 256 = 9,502,720 bytes to flash
; ============================================================

        org     0000h

; =============================================================
; HARDWARE EQUATES
; =============================================================

; --- TMS9918 VDP ports ---------------------------------------

VDP             equ     81h     ; VDP register / address port
VDPD            equ     80h     ; VDP data port

; --- VRAM addresses ------------------------------------------

NAMETBL         equ     0000h   ; Name Table base (40×24 = 960 bytes)
PATTBL          equ     0800h   ; Pattern Generator Table (2048 bytes)

; --- Screen dimensions ---------------------------------------

COLUMNS         equ     40
ROWS            equ     24

; --- SV-608M SASI host adapter ports -------------------------
; Port assignments confirmed against WD1002-SHD OEM Manual.
; Port 46h exposes SASI bus signals: MSG·I/O·C/D·REQ·BSY

SV608M_RESET    equ     40h     ; W: SASI bus reset (active pulse)
SV608M_DATA_OUT equ     41h     ; W: SASI data bus write (auto-ACK)
SV608M_DATA_IN  equ     42h     ; R: SASI data bus read (auto-ACK)
SV608M_SEL      equ     43h     ; W: SEL signal assertion
SV608M_STATUS   equ     46h     ; R: SASI bus status (AND 1Fh)

; --- SASI bus phase values (port 46h AND 1Fh) ----------------

ST_BUS_FREE     equ     00h     ; Bus Free — no target selected
ST_DATA_OUT     equ     03h     ; Data Out phase (host→target)
ST_COMMAND      equ     0Bh     ; Command phase (host→target)
ST_DATA_IN      equ     13h     ; Data In phase (target→host)
ST_STATUS       equ     1Bh     ; Status phase (target→host)
ST_MSG_IN       equ     1Fh     ; Message In phase (target→host)

; --- PicoExpander ports --------------------------------------

PE_COMMAND_PORT equ     13h     ; PicoExpander command port
PE_DATA_PORT    equ     14h     ; PicoExpander data port
PE_CMD_DUMP_DISK equ    07h     ; Enter dump disk mode
PE_CMD_TERMINATE equ    03h     ; Terminate write mode

; --- HDD geometry constants ----------------------------------

TOTAL_TRACKS    equ     1160    ; Physical tracks 0–1159
SECS_PER_TRK    equ     32      ; 256-byte sectors per track

; --- RAM variables (main RAM at 8000h+) ----------------------

cursor_pos      equ     0F000h  ; 2 bytes: VRAM cursor position
cur_track       equ     0F002h  ; 2 bytes: current track (0–1159)
cur_sector      equ     0F004h  ; 1 byte:  current sector (0–31)
linear_addr     equ     0F005h  ; 2 bytes: computed linear address
result_byte     equ     0F007h  ; 1 byte:  controller result byte
sector_buf      equ     0F100h  ; 256 bytes: one sector of data

; =============================================================
; ENTRY POINT
; =============================================================

start:
        di
        ld      sp, 0F000h              ; stack grows down below vars

        ; --- Initialise VDP for Text Mode (40×24) ------------

        ld      hl, vdp_regs
        ld      b, 8                    ; 8 registers (R#0 – R#7)
        ld      c, 80h                  ; register 0 with bit 7 set
init_vdp:
        ld      a, (hl)
        inc     hl
        out     (VDP), a               ; write register data
        ld      a, c
        inc     c
        out     (VDP), a               ; write register number
        djnz    init_vdp

        ; --- Clear name table (fill with spaces) -------------

        xor     a
        out     (VDP), a
        ld      a, 40h                  ; VRAM addr 0000h, write mode
        out     (VDP), a

        ld      a, 20h                  ; space character
        ld      b, 0                    ; 256 iterations per round
        ld      c, 4                    ; 4 rounds = 1024 bytes (>960)
clear_screen:
        out     (VDPD), a
        nop
        djnz    clear_screen
        dec     c
        jr      nz, clear_screen

        ; --- Load font into pattern table --------------------

        xor     a
        out     (VDP), a
        ld      a, 48h                  ; VRAM addr 0800h, write mode
        out     (VDP), a

        ld      hl, font_data
        ld      a, 8                    ; 8 pages × 256 bytes = 2048
        ld      bc, VDPD               ; B=0 (256 iterations), C=port
load_font:
        outi                            ; write (HL) to port C
        jp      nz, load_font
        dec     a
        jp      nz, load_font

        ; --- Turn on screen ----------------------------------

        ld      a, 50h                  ; R#1: BL=1 (display on), M1=1
        out     (VDP), a
        ld      a, 81h                  ; write to register 1
        out     (VDP), a

        ; --- Initialise cursor position ----------------------

        ld      hl, 0
        ld      (cursor_pos), hl

        ; --- Print banner ------------------------------------

        ld      hl, msg_banner
        call    print_line
        ld      hl, msg_empty
        call    print_line

        ; --- Initialise WD1002-SHD controller -----------------
        ; Reset SASI bus and send Set Parameters (0Ch) to
        ; configure the controller for ST-212 geometry.

        ld      hl, msg_init
        call    print_str

        call    sasi_reset
        call    init_controller

        ld      hl, msg_ok
        call    print_line

        ; --- Enter PicoExpander dump disk mode ----------------

        ld      a, PE_CMD_DUMP_DISK
        out     (PE_COMMAND_PORT), a    ; enter dump disk mode
        ld      a, 02h                  ; drive index 2 (HDD)
        out     (PE_DATA_PORT), a

        ; --- Initialise track counter to 0 -------------------

        ld      hl, 0
        ld      (cur_track), hl

; ----- Outer loop: iterate over all tracks --------------------

track_loop:
        ; Check if all tracks have been dumped
        ld      hl, (cur_track)
        ld      de, TOTAL_TRACKS        ; 1160
        or      a                       ; clear carry
        sbc     hl, de
        jp      nc, done                ; track >= 1160 → finished

        ; --- Print progress every 32 tracks (~256K) ----------

        ld      a, (cur_track)          ; low byte of track
        and     1Fh                     ; every 32 tracks
        call    z, print_progress

        ; --- Reset sector counter for this track -------------

        xor     a
        ld      (cur_sector), a

; ----- Inner loop: iterate over 32 sectors per track ----------

sector_loop:
        ; =====================================================
        ; STEP 1: Compute 16-bit linear sector address
        ;         linear = track × 32 + sector
        ;
        ; Multiply track by 32 using 5 left shifts.
        ; =====================================================

        ld      hl, (cur_track)
        add     hl, hl                  ; ×2
        add     hl, hl                  ; ×4
        add     hl, hl                  ; ×8
        add     hl, hl                  ; ×16
        add     hl, hl                  ; ×32
        ld      a, (cur_sector)
        ld      e, a
        ld      d, 0
        add     hl, de                  ; HL = linear sector addr
        ld      (linear_addr), hl

        ; =====================================================
        ; STEP 2: Read one 256-byte sector from the HDD
        ;         via the SV-608M controller protocol.
        ; =====================================================

        call    read_hdd_sector

        ; Check result — A returns the completion byte from
        ; the controller (00h = success, non-zero = error).
        or      a
        jp      nz, read_error

        ; =====================================================
        ; STEP 3: Send 256 data bytes to PicoExpander
        ;
        ; Uses OTIR loop — identical to the floppy dumper's
        ; SEND_SECTOR_TO_PICO routine.  The Pico accumulates
        ; data into an 8K double buffer and flushes each 4K
        ; half to flash as it fills.
        ; =====================================================

        ld      hl, sector_buf
        ld      c, PE_DATA_PORT         ; port 14h
        ld      b, 0                    ; B=0 → 256 iterations
        otir                            ; send 256 bytes

        ; =====================================================
        ; STEP 4: Advance to next sector
        ; =====================================================

        ld      a, (cur_sector)
        inc     a
        ld      (cur_sector), a
        cp      SECS_PER_TRK           ; 32
        jp      nz, sector_loop

        ; All 32 sectors done — advance to next track
        ld      hl, (cur_track)
        inc     hl
        ld      (cur_track), hl
        jp      track_loop

; =============================================================
; COMPLETION / ERROR HANDLERS
; =============================================================

done:
        ; --- Terminate PicoExpander dump disk mode -----------
        ld      a, PE_CMD_TERMINATE
        out     (PE_COMMAND_PORT), a

        ld      hl, msg_done
        call    print_line
halt_done:
        halt
        jp      halt_done               ; stay halted

aborted:
        ld      a, PE_CMD_TERMINATE
        out     (PE_COMMAND_PORT), a
        ld      hl, msg_abort
        call    print_line
halt_abort:
        halt
        jp      halt_abort

read_error:
        ; A contains the error code from the controller
        push    af
        ld      hl, msg_error
        call    print_str
        pop     af
        call    print_hex_byte          ; print error code
        ld      hl, msg_at_track
        call    print_str
        ld      a, (cur_track+1)
        call    print_hex_byte
        ld      a, (cur_track)
        call    print_hex_byte
        ld      a, '/'
        call    putchar
        ld      a, (cur_sector)
        call    print_hex_byte
        call    newline
        ; Terminate PicoExpander on error too
        ld      a, PE_CMD_TERMINATE
        out     (PE_COMMAND_PORT), a
halt_error:
        halt
        jp      halt_error

; =============================================================
; SV-608M CONTROLLER ROUTINES
; =============================================================
; These are standalone equivalents of the BIOS routines
; SUB41 (E393h), SUB53 (E4D9h), SUB50 (E4A3h), SUB43 (E3D0h),
; SUB51 (E4B1h), SUB52 (E4C5h) — rewritten for clarity.

; -------------------------------------------------------------
; read_hdd_sector
;   Reads one 256-byte sector from the HDD via SV-608M.
;   Input:  (linear_addr) = 16-bit sector address
;   Output: sector_buf filled with 256 bytes
;           A = result byte (00h = success)
;   Modifies: AF, BC, HL
; -------------------------------------------------------------

read_hdd_sector:

        ; --- Wait for Bus Free --------------------------------
        ; Equivalent to SUB53 (E4D9h).
        ; Poll SASI bus status until 00h (Bus Free).

wait_bus_free:
        in      a, (SV608M_STATUS)      ; read port 46h
        and     1Fh                     ; mask 5 SASI signals
        jp      nz, wait_bus_free       ; loop until Bus Free

        ; --- Target Selection ---------------------------------
        ; Assert data + SEL to select the WD1002-SHD target.

        ld      a, 01h
        out     (SV608M_DATA_OUT), a    ; port 41h ← target ID
        out     (SV608M_SEL), a         ; port 43h ← assert SEL

        ; --- Command Phase: Send 6-byte SASI command ---------
        ; Each byte: wait for Command phase (0Bh), then write
        ; to SV608M_DATA_OUT (41h).
        ; Equivalent to SUB49 (E48Eh) + SUB50 (E4A3h).

        ld      a, 08h                  ; byte 0: Read opcode
        call    send_cmd_byte
        ld      a, 00h                  ; byte 1: LUN=0, addr[20:16]=0
        call    send_cmd_byte
        ld      a, (linear_addr+1)      ; byte 2: addr[15:8]
        call    send_cmd_byte
        ld      a, (linear_addr)        ; byte 3: addr[7:0]
        call    send_cmd_byte
        ld      a, 01h                  ; byte 4: block count = 1
        call    send_cmd_byte
        ld      a, 47h                  ; byte 5: control (ECC, 15µs step)
        call    send_cmd_byte

        ; --- Data In Phase: Read 256 data bytes --------------
        ; Equivalent to SUB43 (E3D0h).
        ; Poll SASI status until either 13h (Data In) or
        ; 1Bh (Status — error, no data).
        ; On 13h, use INIR to block-read 256 bytes from
        ; SV608M_DATA_IN (42h) into sector_buf.

read_data:
        in      a, (SV608M_STATUS)
        and     1Fh
        cp      ST_STATUS               ; 1Bh = Status phase (no data)?
        jp      z, read_result          ; skip Data In phase
        cp      ST_DATA_IN              ; 13h = Data In phase?
        jp      nz, read_data           ; neither — keep polling

        ld      hl, sector_buf          ; destination buffer
        ld      c, SV608M_DATA_IN       ; source port 42h
        ld      b, 0                    ; B=0 → 256 iterations
        inir                            ; read 256 bytes

        ; --- Status Phase: Read completion result ------------
        ; Equivalent to SUB51 (E4B1h).
        ; Wait for Status phase (1Bh), read result from 42h.

read_result:
        in      a, (SV608M_STATUS)
        and     1Fh
        cp      ST_STATUS               ; 1Bh
        jp      nz, read_result
        in      a, (SV608M_DATA_IN)     ; result byte → A
        ld      (result_byte), a

        ; --- Message In Phase: Read final status -------------
        ; Equivalent to SUB52 (E4C5h).
        ; Wait for Message In (1Fh), read from 42h.

read_final:
        in      a, (SV608M_STATUS)
        and     1Fh
        cp      ST_MSG_IN               ; 1Fh
        jp      nz, read_final
        in      a, (SV608M_DATA_IN)     ; completion byte (discard)

        ld      a, (result_byte)        ; return result in A
        ret

; -------------------------------------------------------------
; send_cmd_byte
;   Sends one command byte to the controller.
;   Waits for STATUS == 0Bh, then writes A to DATA port.
;   Equivalent to SUB50 (E4A3h).
;   Input:  A = byte to send
;   Modifies: flags only (A preserved across wait)
; -------------------------------------------------------------

send_cmd_byte:
        push    af                      ; save the byte to send
send_cmd_wait:
        in      a, (SV608M_STATUS)      ; poll SASI bus status
        and     1Fh
        cp      ST_COMMAND              ; 0Bh = Command phase?
        jp      nz, send_cmd_wait       ; not ready — loop
        pop     af                      ; restore data byte
        out     (SV608M_DATA_OUT), a    ; send to port 41h
        ret

; -------------------------------------------------------------
; send_data_byte
;   Sends one byte during Data Out phase.
;   Waits for STATUS == 03h, then writes A to DATA port.
;   Equivalent to SUB54 (E4EBh).
;   Input:  A = byte to send
;   Modifies: flags only (A preserved across wait)
; -------------------------------------------------------------

send_data_byte:
        push    af                      ; save the byte to send
send_data_wait:
        in      a, (SV608M_STATUS)      ; poll SASI bus status
        and     1Fh
        cp      ST_DATA_OUT             ; 03h = Data Out phase?
        jp      nz, send_data_wait      ; not ready — loop
        pop     af                      ; restore data byte
        out     (SV608M_DATA_OUT), a    ; send to port 41h
        ret

; =============================================================
; SASI CONTROLLER INITIALISATION
; =============================================================
; These routines replicate the BIOS initialisation sequence
; (SUB39 + SUB47/SUB48 at E227h/E449h/E463h), so the program
; does not depend on any prior BIOS initialisation.

; -------------------------------------------------------------
; sasi_reset
;   Resets the SASI bus by pulsing the reset line on port 40h.
;   Equivalent to SUB39 (E227h).
;   Modifies: flags, B
; -------------------------------------------------------------

sasi_reset:
        out     (SV608M_RESET), a       ; pulse SASI bus reset
        push    bc
        ld      b, 3Ch                  ; delay: 60 NOPs (~60 µs)
sasi_reset_delay:
        nop
        dec     b
        jp      nz, sasi_reset_delay
        pop     bc
        ret

; -------------------------------------------------------------
; init_controller
;   Sends the SASI Set Parameters command (0Ch) to configure
;   the WD1002-SHD for the ST-212 drive geometry.
;   Equivalent to SUB47 (E449h) + SUB48 (E463h).
;
;   Sequence:
;     1. Wait Bus Free + Target Selection
;     2. Command phase: send 6-byte CDB (opcode 0Ch)
;     3. Data Out phase: send 8-byte parameter block
;     4. Status phase: read result
;     5. Message In phase: read completion
;
;   The 8-byte parameter block configures:
;     - Cylinders:     306 (01h, 32h)
;     - Heads:         4   (04h)
;     - RWC cylinder:  128 (00h, 80h)
;     - WP cylinder:   64  (00h, 40h)
;     - ECC burst len: 11  (0Bh)
;
;   Modifies: AF
; -------------------------------------------------------------

init_controller:
        ; --- Wait for Bus Free + Target Selection ----
        ; Equivalent to SUB53 (E4D9h).

init_bus_free:
        in      a, (SV608M_STATUS)
        and     1Fh
        jp      nz, init_bus_free       ; wait for Bus Free (00h)
        ld      a, 01h
        out     (SV608M_DATA_OUT), a    ; target ID on data bus
        out     (SV608M_SEL), a         ; assert SEL

        ; --- Command phase: 6-byte CDB for Set Parameters ----
        ; CDB: 0Ch 00h 00h 00h 00h 00h
        ; Equivalent to SUB49 (E48Eh).

        ld      a, 0Ch                  ; byte 0: Set Parameters opcode
        call    send_cmd_byte
        ld      a, 00h                  ; byte 1: LUN 0
        call    send_cmd_byte
        ld      a, 00h                  ; byte 2: reserved
        call    send_cmd_byte
        ld      a, 00h                  ; byte 3: reserved
        call    send_cmd_byte
        ld      a, 00h                  ; byte 4: reserved
        call    send_cmd_byte
        ld      a, 00h                  ; byte 5: reserved
        call    send_cmd_byte

        ; --- Data Out phase: 8-byte parameter block -----------
        ; Equivalent to SUB48 (E463h) using SUB54 (E4EBh).
        ; These values match the CP/M BIOS configuration for
        ; the ST-212 hard disk.

        ld      a, 01h                  ; cylinders high byte
        call    send_data_byte
        ld      a, 32h                  ; cylinders low byte (0x0132 = 306)
        call    send_data_byte
        ld      a, 04h                  ; number of heads (4)
        call    send_data_byte
        ld      a, 00h                  ; RWC high byte
        call    send_data_byte
        ld      a, 80h                  ; RWC low byte (0x0080 = 128)
        call    send_data_byte
        ld      a, 00h                  ; WP high byte
        call    send_data_byte
        ld      a, 40h                  ; WP low byte (0x0040 = 64)
        call    send_data_byte
        ld      a, 0Bh                  ; max ECC burst length (11 bits)
        call    send_data_byte

        ; --- Status phase: read result -----------------------

init_status:
        in      a, (SV608M_STATUS)
        and     1Fh
        cp      ST_STATUS               ; 1Bh
        jp      nz, init_status
        in      a, (SV608M_DATA_IN)     ; read status (discard)

        ; --- Message In phase: read completion ---------------

init_msg_in:
        in      a, (SV608M_STATUS)
        and     1Fh
        cp      ST_MSG_IN               ; 1Fh
        jp      nz, init_msg_in
        in      a, (SV608M_DATA_IN)     ; read message (discard)

        ret

; =============================================================
; VDP TEXT OUTPUT ROUTINES
; =============================================================
; Adapted from the PicoExpander floppy dumper (dumper.asm).
; Uses TMS9918 text mode: 40 columns × 24 rows at VRAM 0000h.

; -------------------------------------------------------------
; set_vram_write — Set VRAM address for writing.
;   Input: HL = VRAM address
; -------------------------------------------------------------

set_vram_write:
        ld      a, l
        out     (VDP), a
        ld      a, h
        or      40h                     ; set write bit
        out     (VDP), a
        ret

; -------------------------------------------------------------
; putchar — Write one character at cursor, advance cursor.
;   Input: A = character
; -------------------------------------------------------------

putchar:
        push    hl
        push    af
        ld      hl, (cursor_pos)
        call    set_vram_write
        pop     af
        out     (VDPD), a
        ld      hl, (cursor_pos)
        inc     hl
        ld      (cursor_pos), hl
        pop     hl
        ret

; -------------------------------------------------------------
; newline — Move cursor to start of next line.
; -------------------------------------------------------------

newline:
        push    hl
        push    de
        push    af
        ld      hl, (cursor_pos)
        ld      de, COLUMNS
        xor     a
        ld      b, h
        ld      c, l
newline_div:
        sbc     hl, de
        jr      c, newline_done
        inc     a
        jr      newline_div
newline_done:
        inc     a                       ; next row
        ld      hl, 0
        ld      de, COLUMNS
newline_mul:
        add     hl, de
        dec     a
        jr      nz, newline_mul
        ld      (cursor_pos), hl
        pop     af
        pop     de
        pop     hl
        ret

; -------------------------------------------------------------
; print_str — Print null-terminated string (no newline).
;   Input: HL = pointer to string
; -------------------------------------------------------------

print_str:
        ld      a, (hl)
        or      a
        ret     z
        call    putchar
        inc     hl
        jr      print_str

; -------------------------------------------------------------
; print_line — Print null-terminated string + newline.
;   Input: HL = pointer to string
; -------------------------------------------------------------

print_line:
        call    print_str
        call    newline
        ret

; -------------------------------------------------------------
; print_hex_byte — Print byte in A as two hex digits.
; -------------------------------------------------------------

print_hex_byte:
        push    af
        rra                             ; high nibble → low
        rra
        rra
        rra
        call    print_hex_nibble
        pop     af
print_hex_nibble:
        and     0Fh
        add     a, 30h                  ; '0'
        cp      3Ah                     ; > '9'?
        jp      c, print_hex_out
        add     a, 07h                  ; adjust to 'A'–'F'
print_hex_out:
        call    putchar
        ret

; =============================================================
; UTILITY: PROGRESS DISPLAY
; =============================================================

; Print current track number to the screen.
; Called every 32 tracks.

print_progress:
        push    af
        push    bc
        push    de
        push    hl
        ld      hl, msg_track
        call    print_str
        ld      a, (cur_track+1)        ; track high byte
        call    print_hex_byte
        ld      a, (cur_track)          ; track low byte
        call    print_hex_byte
        ld      hl, msg_of_total
        call    print_line
        pop     hl
        pop     de
        pop     bc
        pop     af
        ret

; =============================================================
; MESSAGES (null-terminated)
; =============================================================

msg_banner:
        defb    'SV-608M HDD Dump v1.0', 0

msg_empty:
        defb    0

msg_init:
        defb    'Init controller... ', 0

msg_ok:
        defb    'OK', 0

msg_track:
        defb    'Track ', 0

msg_of_total:
        defb    'h/0488h', 0            ; 0488h = 1160 in hex

msg_done:
        defb    'Dump complete. 37120 sectors transferred.', 0

msg_abort:
        defb    'Aborted.', 0

msg_error:
        defb    'Read error: ', 0

msg_at_track:
        defb    'h at track ', 0

; =============================================================
; VDP REGISTER TABLE (Text Mode 40×24)
; =============================================================

vdp_regs:
        defb    00h             ; R#0: M3=0
        defb    10h             ; R#1: M1=1 (text mode), BL=0 (screen off)
        defb    00h             ; R#2: Name Table at 0000h
        defb    00h             ; R#3: Color Table (N/A in text mode)
        defb    01h             ; R#4: Pattern Table at 0800h
        defb    00h             ; R#5: SAT (N/A in text mode)
        defb    00h             ; R#6: SPT (N/A in text mode)
        defb    0F1h            ; R#7: Text=white(F), Background=black(1)

; =============================================================
; FONT DATA (256 chars × 8 bytes = 2048 bytes)
; =============================================================
; Supply a standard 8×8 font here.  The PicoExpander floppy
; dumper uses rom/asm/dumper/font8x8.asm which can be reused:
;
;   INCLUDE "font8x8.asm"

font_data:
        ; (font data to be included here)

; =============================================================
; END
; =============================================================
