Jay Taylor's notes

back to listing index

pdd.sh/pdd.sh at main · bkw777/pdd.sh

[web search]
Original source (github.com)
Tags: bash code unholy dirty beautiful kung-fu github.com
Clipped on: 2022-02-15

Skip to content
main

pdd.sh / pdd.sh

Go to file
Image (Asset 2/2) alt= History
1 contributor
executable file 1814 lines (1588 sloc) 53.4 KB
1 #!/usr/bin/env bash
2 # pdd.sh - Tandy Portable Disk Drive client in pure bash
3 # Brian K. White b.kenyon.w@gmail.com
4 # github.com/bkw777/pdd.sh
5 # https://archive.org/details/tandy-service-manual-26-3808-s-software-manual-for-portable-disk-drive
6 # http://bitchin100.com/wiki/index.php?title=TPDD-2_Sector_Access_Protocol
7 # https://trs80stuff.net/tpdd/tpdd2_boot_disk_backup_log_hex.txt
8
9 ###############################################################################
10 # CONFIG
11 #
12
13 ###############################################################################
14 # behavior
15
16 # 1 or 2 for TPDD1 or TPDD2
17 : ${TPDD_MODEL:=1}
18
19 # verbose/debug
20 # 0/unset=normal, 1=verbose, >1=more verbose, 3=log all tty traffic to files
21 # DEBUG=1 ./pdd ...
22 v=${DEBUG:-0}
23
24 # true/false - Automatically convert filenames to Floppy/Flopy2-compatible 6.2 .
25 # When saving, pad to %-6s.%-2s : "A.BA" -> "A .BA"
26 # When loading, strip all spaces : "A .BA" -> "A.BA"
27 # You need to do this to be compatible with Floppy, Flopy2, TS-DOS, etc.
28 # But the drive doesn't care, and Model T's are not the only TPDD users(1).
29 # So the automatic 6.2 padding can be disabled by setting this to false.
30 # This can be done at run-time from the commandline:
31 # $> FLOPPY_COMPAT=false ./pdd save myfilename.exe
32 # Even when enabled, if a filename wouldn't fit within 6.2, then it is
33 # not modified, so enabled generally does the expected thing automatically.
34 # (1) TANDY WP-2, Brother knitting machines, Cambridge Z-88, MS-DOS & most OS's
35 : ${FLOPPY_COMPAT:=true}
36
37 # Default rs232 tty device name, with platform differences
38 # The automatic TPDD port detection will search "/dev/${TPDD_TTY_PREFIX}*"
39 stty_f="-F" TPDD_TTY_PREFIX=ttyUSB # linux
40 case "${OSTYPE,,}" in
41 *bsd*) stty_f="-f" TPDD_TTY_PREFIX=ttyU ;; # *bsd
42 darwin*) stty_f="-f" TPDD_TTY_PREFIX=cu.usbserial- ;; # osx
43 esac
44
45 # stty flags to set the serial port parameters & tty behavior
46 # For 9600-only drives like FB-100 or FDD19, change 19200 to 9600
47 # (FB-100/FDD19 can run at 19200 by removing the solder blob from dip switch 1)
48 # To disable RTS/CTS hardware flow control, change "crtscts" to "-crtscts"
49 : ${BAUD:=19200}
50 STTY_FLAGS='crtscts clocal cread raw pass8 flusho -echo'
51
52 ###############################################################################
53 # tunables
54
55 # tty read timeout in ms
56 # When issuing the "read" command to read bytes from the serial port, wait this
57 # long (in ms) for a byte to appear before giving up.
58 TTY_READ_TIMEOUT_MS=50
59
60 # Default tpdd_wait() timout in ms
61 # Wait this long (by default) for the drive to respond after issuing a command.
62 # Some commands like dirent(get_first) and close can take 2 seconds to respond.
63 # Some commands like format take 100 seconds.
64 TPDD_WAIT_TIMEOUT_MS=5000
65 TPDD_WAIT_PERIOD_MS=100
66
67 # How long to wait for format to complete
68 # Usually takes just under 100 seconds.
69 # If you ever get a timeout while formatting, increase this by 1000 until
70 # you no longer get timeouts.
71 FORMAT_WAIT_MS=105000
72 FORMAT_TPDD2_EXTRA_WAIT_MS=10000
73
74 # How long to wait for delete to complete
75 # Delete takes from 3 to 20 seconds. Larger files take longer.
76 # 65534 byte file takes 20.2 seconds, so 30 seconds should be safe.
77 DELETE_WAIT_MS=30000
78
79 # How long to wait for close to complete
80 CLOSE_WAIT_MS=20000
81
82 # The initial dirent(get_first) for a directory listing
83 LIST_WAIT_MS=10000
84
85 # Per-byte delay in send_loader()
86 LOADER_PER_CHAR_MS=6
87
88 #
89 # CONFIG
90 ###############################################################################
91
92 ###############################################################################
93 # CONSTANTS
94 #
95
96 ###############################################################################
97 # operating modes
98 typeset -ra mode=(
99 [0]=fdc # operate a TPDD1 drive in "FDC mode"
100 [1]=opr # operate a TPDD1 drive "operation mode"
101 [2]=pdd2 # operate a TPDD2 drive ("operation mode" with more commands)
102 [3]=loader # send an ascii BASIC file and BASIC_EOF out the serial port
103 [4]=server # vaporware
104 )
105
106 ###############################################################################
107 # "Operation Mode" constants
108
109 # Operation Mode Request/Return Block Formats
110 typeset -rA opr_fmt=(
111 # requests
112 [req_dirent]='00'
113 [req_open]='01'
114 [req_close]='02'
115 [req_read]='03'
116 [req_write]='04'
117 [req_delete]='05'
118 [req_format]='06'
119 [req_status]='07'
120 [req_fdc]='08'
121 [req_condition]='0C' # TPDD2
122 [req_sector_cache]='30' # TPDD2
123 [req_write_cache]='31' # TPDD2
124 [req_read_cache]='32' # TPDD2
125 # returns
126 [ret_read]='10'
127 [ret_dirent]='11'
128 [ret_std]='12' # error open close delete status write
129 [ret_condition]='15' # TPDD2
130 [ret_pdd2_sector_std]='38' # TPDD2 sector_cache write_cache
131 [ret_read_cache]='39' # TPDD2
132 )
133
134 # Operation Mode Error Codes
135 typeset -rA opr_msg=(
136 [00]='Operation Complete'
137 [10]='File Not Found'
138 [11]='File Exists'
139 [30]='Command Parameter or Sequence Error'
140 [31]='Directory Search Error'
141 [35]='Bank Error'
142 [36]='Parameter Error'
143 [37]='Open Format Mismatch'
144 [3F]='End of File'
145 [40]='No Start Mark'
146 [41]='ID CRC Check Error'
147 [42]='Sector Length Error'
148 [43]='Read Error 3'
149 [44]='Format Verify Error'
150 [45]='Disk Not Formatted'
151 [46]='Format Interruption'
152 [47]='Erase Offset Error'
153 [48]='Read Error 8'
154 [49]='DATA CRC Check Error'
155 [4A]='Sector Number Error'
156 [4B]='Read Data Timeout'
157 [4C]='Read Error C'
158 [4D]='Sector Number Error'
159 [4E]='Read Error E'
160 [4F]='Read Error F'
161 [50]='Write-Protected Disk'
162 [5E]='Disk Not Formatted'
163 [60]='Disk Full or Max File Size Exceeded or Directory Full' # TPDD2 'Directory Full'
164 [61]='Disk Full'
165 [6E]='File Too Long'
166 [70]='No Disk'
167 [71]='Disk Not Inserted or Disk Change Error' # TPDD2 'Disk Change Error'
168 [72]='Disk Insertion Error 2'
169 [73]='Disk Insertion Error 3'
170 [74]='Disk Insertion Error 4'
171 [75]='Disk Insertion Error 5'
172 [76]='Disk Insertion Error 6'
173 [77]='Disk Insertion Error 7'
174 [78]='Disk Insertion Error 8'
175 [79]='Disk Insertion Error 9'
176 [7A]='Disk Insertion Error A'
177 [7B]='Disk Insertion Error B'
178 [7C]='Disk Insertion Error C'
179 [7D]='Disk Insertion Error D'
180 [7E]='Disk Insertion Error E'
181 [7F]='Disk Insertion Error F'
182 [80]='Hardware Fault 0'
183 [81]='Hardware Fault 1'
184 [82]='Hardware Fault 2'
185 [83]='Defective Disk (power-cycle to clear error)'
186 [84]='Hardware Fault 4'
187 [85]='Hardware Fault 5'
188 [86]='Hardware Fault 6'
189 [87]='Hardware Fault 7'
190 [88]='Hardware Fault 8'
191 [89]='Hardware Fault 9'
192 [8A]='Hardware Fault A'
193 [8B]='Hardware Fault B'
194 [8C]='Hardware Fault C'
195 [8D]='Hardware Fault D'
196 [8E]='Hardware Fault E'
197 [8F]='Hardware Fault F'
198 )
199
200 # Directory Entry Search Forms
201 typeset -rA dirent_cmd=(
202 [set_name]=0
203 [get_first]=1
204 [get_next]=2
205 )
206
207 # File Open Access Modes
208 typeset -rA open_mode=(
209 [write_new]=1
210 [write_append]=2
211 [read]=3
212 )
213
214 ###############################################################################
215 # "FDC Mode" constants
216
217 # FDC Mode Commands
218 typeset -rA fdc_cmd=(
219 [mode]='M'
220 [condition]='D'
221 [format]='F'
222 [format_nv]='G'
223 [read_id]='A'
224 [read_sector]='R'
225 [search_id]='S'
226 [write_id]='B'
227 [write_id_nv]='C'
228 [write_sector]='W'
229 [write_sector_nv]='X'
230 )
231
232 # FDC Mode Errors
233 # There is no documentation for the FDC error codes
234 # These are guesses from experimenting
235 typeset -ra fdc_msg=(
236 [0]='OK'
237 [17]='Logical Sector Number Below Range'
238 [18]='Logical Sector Number Above Range'
239 [19]='Physical Sector Number Above Range'
240 [33]='Parameter Invalid, Wrong Type'
241 [50]='Invalid Logical Sector Size Code'
242 [51]='Logical Sector Size Code Above Range'
243 [160]='Disk Not Formatted'
244 [161]='Read Error'
245 [176]='Write-Protected Disk'
246 [193]='Invalid Command'
247 [209]='Disk Not Inserted'
248 )
249
250 # FDC Format Disk Logical Sector Size Codes
251 typeset -ra fdc_format_sector_size=(
252 [0]=64
253 [1]=80
254 [2]=128
255 [3]=256
256 [4]=512
257 [5]=1024
258 [6]=1280
259 )
260
261 ###############################################################################
262 # general constants
263
264 typeset -ri
265 PHYSICAL_SECTOR_LENGTH=1280
266 PHYSICAL_SECTOR_COUNT=80
267 PDD1_SECTOR_ID_LENGTH=12
268 TPDD_MAX_FILE_LENGTH=65534
269 TPDD_MAX_FILE_COUNT=40
270 PDD2_SECTOR_CHUNK_LENGTH=64
271 SMT_OFFSET=1240
272 SMT_LENGTH=20
273
274 typeset -r BASIC_EOF='1A'
275
276 #
277 # CONSTANTS
278 ###############################################################################
279
280 ###############################################################################
281 # generic/util functions
282
283 abrt () {
284 echo "$0: $@" >&2
285 exit 1
286 }
287
288 vecho () {
289 local -i l="$1" ;shift
290 ((v>=l)) && echo "$@" >&2
291 :
292 }
293
294 _sleep () {
295 local x
296 read -t ${1:-1} -u 4 x
297 :
298 }
299
300 # Milliseconds to seconds, up to 999999ms
301 ms_to_s () {
302 ((_s=1000000+$1))
303 _s="${_s:1:-3}.${_s: -3}"
304 }
305
306 # Convert a plain text string to hex pairs stored in shex[]
307 str_to_shex () {
308 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
309 local x="$*" ;local -i i l=${#x} ;shex=()
310 for ((i=0;i<l;i++)) { printf -v shex[i] '%02X' "'${x:i:1}" ; }
311 vecho 1 "$z: shex=(${shex[*]})"
312 }
313
314 # Read a local file into hex pairs stored in fhex[]
315 file_to_fhex () {
316 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
317 local x ;fhex=()
318
319 [[ -r "$1" ]] || { err_msg+=(""$1" not found") ;return 1 ; }
320
321 exec 5<"$1" || return $?
322 while IFS= read -d '' -r -n 1 -u 5 x ;do
323 printf -v x '%02X' "'$x"
324 fhex+=($x)
325 ((${#fhex[*]}>TPDD_MAX_FILE_LENGTH)) && { err_msg+=(""$1" exceeds $TPDD_MAX_FILE_LENGTH bytes") ; break ; }
326 done
327 exec 5<&-
328
329 ((${#err_msg[*]})) && return 1
330 vecho 1 "$z: bytes read: ${#fhex[*]}"
331 }
332
333 # Progress indicator
334 # pbar part whole [units]
335 # pbar 14 120
336 # [####====================================] 11%
337 # pbar 14 120 seconds
338 # [####====================================] 11% (14/120 seconds)
339 pbar () {
340 ((v)) && return
341 local -i i c p=$1 w=$2 p_len w_len=40 ;local b= s= u=$3
342 ((w)) && c=$((p*100/w)) p_len=$((p*w_len/w)) || c=100 p_len=$w_len
343 for ((i=0;i<w_len;i++)) { ((i<p_len)) && b+='#' || b+='.' ; }
344 printf 'r%79sr[%s] %d%%%s ' '' "$b" "$c" "${u:+ ($p/$w $u)}"
345 }
346
347 # Busy-indicator
348 typeset -ra _Y=('-' '' '|' '/')
349 spin () {
350 ((v)) && return
351 case "$1" in
352 '+') printf ' ' ;;
353 '-') printf 'bb bb' ;;
354 *) printf 'bb%s ' "${_Y[_y]}" ;;
355 esac
356 ((++_y>3)) && _y=0
357 }
358
359 _init () {
360 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
361 ((_om==operation_mode)) && return
362 _om=${operation_mode}
363 case "${mode[operation_mode]}" in
364 fdc|opr)
365 # ensure we always leave the drive in operation mode
366 trap 'fcmd_mode 1' EXIT
367 # ensure we always start in operation mode
368 fcmd_mode 1
369 ;;
370 *)
371 trap '' EXIT
372 ;;
373 esac
374 }
375
376 ###############################################################################
377 # Main Command Dispatcher
378
379 do_cmd () {
380 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
381 local -i _i _e ;local _a=$@ _c ifs=$IFS IFS=';' ;_a=(${_a}) IFS=$ifs
382 for ((_i=0;_i<${#_a[*]};_i++)) {
383 set ${_a[_i]}
384 _c=$1 ;shift
385 _e=999 err_msg=()
386
387 vecho 2 "$z: ${_c} $@"
388
389 # commands that do not need _init()
390 case "${_c}" in
391 1|pdd1|tpdd1) pdd2=0 operation_mode=1 _e=$? ;;
392 2|pdd2|tpdd2) pdd2=1 operation_mode=2 _e=$? ;;
393 b|bank) ((pdd2)) && bank=$1 _e=$? || abrt "${_c} requires TPDD2" ;;
394 baud|speed) BAUD=$1 _e=$? ;;
395 com_test) lcmd_com_test ;_e=$? ;; # check if port open
396 com_open) lcmd_com_open ;_e=$? ;; # open the port
397 com_close) lcmd_com_close ;_e=$? ;; # close the port
398 sync|drain) tpdd_drain ;_e=$? ;;
399 sum) calc_cksum $* ;_e=$? ;;
400 ocmd_check_err) ocmd_check_err ;_e=$? ;;
401 send_loader) srv_send_loader "$@" ;_e=$? ;;
402 sleep) _sleep $* ;_e=$? ;;
403 debug) ((${#1})) && v=$1 || { ((v)) && v=0 || v=1 ; } ;_e=$? ;;
404 q|quit|bye|exit) exit ;;
405 pdd1_boot) pdd1_boot "$@" ;_e=$? ;; # [100|200]
406 pdd2_boot) pdd2_boot "$@" ;_e=$? ;; # [100|200]
407 '') _e=0 ;;
408 esac
409 ((_e<256)) && {
410 ((${#err_msg[*]})) && printf 'n%s: %sn' "${_c}" "${err_msg[*]}" >&2
411 continue
412 }
413
414 # commands that need _init()
415 _e=0
416 _init
417
418 case "${_c}" in
419
420 # operation-mode commands
421 # TPDD1 & TPDD2 file access
422 # All of the drive firmware "operation mode" functions.
423 # Most of these are low-level, not used directly by a user.
424 # Higher-level commands like ls, load, & save are built out of these.
425 dirent) ocmd_dirent "$@" ;_e=$? ;;
426 open) ocmd_open $* ;_e=$? ;;
427 close) ocmd_close ;_e=$? ;;
428 read) ocmd_read $* ;_e=$? ;;
429 write) ocmd_write $* ;_e=$? ;;
430 delete) ocmd_delete ;_e=$? ;;
431 format) ocmd_format ;_e=$? ;;
432 status) ocmd_status ;_e=$? ;((_e)) || echo "OK" ;;
433
434 # TPDD1-only operation-mode command to switch to fdc-mode
435 fdc) ocmd_fdc ;_e=$? ;;
436
437 # fdc-mode commands
438 # TPDD1 sector access
439 # All of the drive firmware "FDC mode" functions.
440 ${fdc_cmd[mode]}|mode) fcmd_mode $* ;_e=$? ;; # select operation-mode or fdc-mode
441 ${fdc_cmd[condition]}|condition) ((pdd2)) && { pdd2_condition $* ;_e=$? ; } || { fcmd_condition $* ;_e=$? ; } ;; # get drive condition
442 ${fdc_cmd[format]}|fdc_format|ff) fcmd_format $* ;_e=$? ;; # format disk - selectable sector size
443 #${fdc_cmd[format_nv]}|format_nv) fcmd_format_nv $* ;_e=$? ;; # format disk no verify
444 ${fdc_cmd[read_id]}|read_id|ri) fcmd_read_id $* ;_e=$? ;; # read id
445 ${fdc_cmd[read_sector]}|read_logical|rl) fcmd_read_logical $* ;_e=$? ;; # read one logical sector
446 #${fdc_cmd[search_id]}|search_id|si) fcmd_search_id $* ;_e=$? ;; # search id
447 ${fdc_cmd[write_id]}|write_id|wi) fcmd_write_id $* ;_e=$? ;; # write id
448 #${fdc_cmd[write_id_nv]}|write_id_nv) fcmd_write_id_nv $* ;_e=$? ;; # write id no verify
449 ${fdc_cmd[write_sector]}|write_logical|wl) fcmd_write_logical $* ;_e=$? ;; # write sector
450 #${fdc_cmd[write_sect_nv]}|write_sector_nv) fcmd_write_sector_nv $* ;_e=$? ;; # write sector no verify
451
452 # TPDD2 sector access
453 read_cache) pdd2_read_cache $* ;_e=$? ;; # read from cache
454 write_cache) lcmd2_write_cache $* ;_e=$? ;; # write to cache
455 sector_cache) pdd2_sector_cache $* ;_e=$? ;; # copy sector between disk & cache
456
457 # TPDD1 & TPDD2 local/client commands
458 ls|dir) lcmd_ls "$@" ;_e=$? ;;
459 rm|del) lcmd_rm "$@" ;_e=$? ;;
460 load) lcmd_load "$@" ;_e=$? ;;
461 save) lcmd_save "$@" ;_e=$? ;;
462 mv|ren) lcmd_mv "$@" ;_e=$? ;;
463 cp|copy) lcmd_cp "$@" ;_e=$? ;;
464 rp|read_physical) ((pdd2)) || { pdd1_read_physical "$@" ;_e=$? ; } ;;
465 dd|dump_disk) ((pdd2)) && { pdd2_dump_disk "$@" ;_e=$? ; } || { pdd1_dump_disk "$@" ;_e=$? ; } ;;
466 rd|restore_disk) ((pdd2)) && { pdd2_restore_disk "$@" ;_e=$? ; } || { pdd1_restore_disk "$@" ;_e=$? ; } ;;
467
468 # low level manual raw/debug commands
469 tpdd_read) tpdd_read $* ;_e=$? ;; # read $1 bytes
470 tpdd_write) tpdd_write $* ;_e=$? ;; # write $* (hex pairs)
471 ocmd_send_req) ocmd_send_req $* ;_e=$? ;;
472 ocmd_read_ret) ocmd_read_ret $* ;_e=$? ;;
473 read_smt) read_smt $* ;_e=$? ;;
474
475
476 *) echo "Unknown command: "${_c}"" >&2 ;;
477 esac
478 ((${#err_msg[*]})) && printf 'n%s: %sn' "${_c}" "${err_msg[*]}" >&2
479 }
480 return ${_e}
481 }
482
483 ###############################################################################
484 # experimental junk
485
486 # Emulate a client performing the TPDD1 boot sequence
487 pdd1_boot () {
488 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
489 local REPLY M mdl=${1:-100}
490
491 close_com
492 open_com 9600
493
494 echo -en "------ TDPP1 (26-3808) bootstrap ------n"
495 "Turn the drive power OFF.n"
496 "Set all 4 dip switches to ON.n"
497 "Insert a TPDD1 (26-3808) Utility Disk.n"
498 "Turn the drive power ON.n"
499 "Press [Enter] when ready: " >&2
500 read -s ;echo
501
502 echo
503 echo "0' $0: $z($@)"
504 echo "0' TPDD1 Boot Sequence - Model $mdl"
505
506 str_to_shex 'S10985157C00AD7EF08B3AS901FE'
507 tpdd_write ${shex[*]} 0D
508
509 tpdd_read_BASIC
510 echo
511
512 # 10 PRINT"---INITIAL PROGRAM LOADER---"
513 # 20 PRINT" WAIT A MINUTE!":CLOSE
514 close_com
515
516 # 30 IF PEEK(1)=171 THEN M2=1 ELSE M2=0
517 # Model 102: 167 -> M2=0
518 # Model 200: 171 -> M2=1
519 case "$mdl" in
520 "200") M='01' ;;
521 *) M='00' ;;
522 esac
523
524 # 40 OPEN "COM:88N1DNN" FOR OUTPUT AS #1
525 open_com 9600
526
527 # 50 ?#1,"KK"+CHR$(M2);
528 # no trailing CR or LF
529 tpdd_write 4B 4B $M
530
531 # 60 FOR I=1 TO 10:NEXT:CLOSE
532 # 1000 = 2 seconds
533 _sleep 0.02
534 close_com
535
536 # 70 LOAD "COM:88N1ENN",R
537 open_com 9600
538 tpdd_read_BASIC
539 close_com
540 echo
541
542 # cycle the port (close & open) between reads
543 echo -e " Don't trust the following binary reads.n"
544 "They come out a little different every time.n"
545 "tpdd_read_unknown() isn't good enough yet.n" >&2
546
547 # collect some binary
548 open_com 9600
549 tpdd_read_unknown
550 close_com
551 printf '%snn' "${rhex[*]}"
552
553 # collect some more binary
554 open_com 9600
555 tpdd_read_unknown
556 close_com
557 printf '%snn' "${rhex[*]}"
558
559 # collect some more binary
560 open_com 9600
561 tpdd_read_unknown
562 close_com
563 printf '%snn' "${rhex[*]}"
564
565 echo -e " IPL done.n"
566 "Turn the drive power OFF.n"
567 "Set all 4 dip switches to OFF.n"
568 "Turn the drive power ON." >&2
569
570 open_com
571 }
572
573 # Emulate a client performing the TPDD2 boot sequence
574 # pdd2_boot [100|200]
575 pdd2_boot () {
576 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
577 local REPLY M mdl=${1:-100}
578
579 echo -en "------ TDPP2 (26-3814) bootstrap ------n"
580 "Turn the drive power OFF.n"
581 "Insert a TPDD2 (26-3814) Utility Disk.n"
582 "(Verify that the write-protect hole is OPEN)n"
583 "Leave the drive power OFF.n"
584 "Press [Enter] when ready: " >&2
585 read -s
586 echo -e "nnNow turn the drive power ON." >&2
587
588
589 echo
590 echo "0' $0: $z($@)"
591 echo "0' TPDD2 Boot Sequence - Model $mdl"
592
593 # RUN "COM:98N1ENN"
594 tpdd_read_BASIC
595
596 # 10 CLS:?"---INITIAL PROGRAM LOADER II---
597 # 20 ?" WAIT A MINUTE!":CLOSE
598 close_com
599 echo
600
601 # 30 IF PEEK(1)=171 THEN M=4 ELSE M=3
602 # Model 102: 167 -> M=3
603 # Model 200: 171 -> M=4
604 case "$mdl" in
605 "200") M='04' ;;
606 *) M='03' ;;
607 esac
608
609 # 40 OPEN"COM:98N1DNN" FOR OUTPUT AS #1
610 open_com
611
612 # 50 ?#1,"FF";CHR$(M);
613 # no trailing CR or LF
614 tpdd_write 46 46 $M
615
616 # 60 FOR I=1 TO 10:NEXT:CLOSE
617 # 1000 = 2 seconds
618 _sleep 0.02
619 close_com
620
621 # 70 RUN"COM:98N1ENN
622 open_com
623 tpdd_read_BASIC
624 close_com
625 echo
626
627 # cycle the port (close & open) between reads
628 echo -e " Don't trust the following binary reads.n"
629 "They come out a little different every time.n"
630 "tpdd_read_unknown() isn't good enough yet.n" >&2
631
632 # collect binary
633 open_com
634 tpdd_read_unknown
635 close_com
636 printf '%snn' "${rhex[*]}"
637
638 # collect binary
639 open_com
640 tpdd_read_unknown
641 close_com
642 printf '%snn' "${rhex[*]}"
643
644 # collect binary
645 open_com
646 tpdd_read_unknown
647 close_com
648 printf '%snn' "${rhex[*]}"
649
650 open_com
651 }
652
653 ###############################################################################
654 # serial port operations
655
656 get_tpdd_port () {
657 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
658 local x=(/dev/${TPDD_TTY_PREFIX#/dev/}*)
659 [[ "${x[0]}" == "/dev/${TPDD_TTY_PREFIX}*" ]] && x=(/dev/tty*)
660 ((${#x[*]}==1)) && { PORT=${x[0]} ;return ; }
661 local PS3="Which serial port is the TPDD drive on? "
662 select PORT in ${x[*]} ;do [[ -c "$PORT" ]] && break ;done
663 }
664
665 test_com () {
666 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
667 { : >&3 ; } 2>&-
668 }
669
670 open_com () {
671 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
672 local b=${1:-$BAUD}
673 test_com && return
674 exec 3<>"${PORT}"
675 stty ${stty_f} "${PORT}" $b ${STTY_FLAGS}
676 test_com || abrt "Failed to open serial port "${PORT}""
677 }
678
679 close_com () {
680 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
681 exec 3>&-
682 }
683
684 ###############################################################################
685 # TPDD communication primitives
686
687 # write $* to com port as binary
688 tpdd_write () {
689 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
690 local x=$*
691 ((v==9)) && { local c=$((10000+seq++)) ;printf '%b' "x${x// /x}" >${0##*/}.$$.${c#?}.$z ; }
692 printf '%b' "x${x// /x}" >&3
693 }
694
695 # read $1 bytes from com port
696 # store each byte as a hex pair in global rhex[]
697 #
698 # We need to read binary data from the drive. The special problem with handling
699 # binary data in shell is that it's not possible to store or retrieve null/0x00
700 # bytes in a shell variable. All other values can be handled with a little care.
701 #
702 # But we can *detect* null bytes and we can store the knowledge of them instead.
703 #
704 # LANG=C IFS= read -r -d $'0' gets us all bytes except 0x00.
705 #
706 # To get the 0x00's what we do here is tell read() to treat null as the
707 # delimiter, then read one byte at a time for the expected number of bytes (in
708 # the case of TPDD, we always know the expected number of bytes, but we could
709 # also read until end of data). For each read, the variable holding the data
710 # will be empty if we read a 0x00 byte, or if there was no data. For each byte
711 # that comes back empty, look at the return value from read() to tell whether
712 # the drive sent a 0x00 byte, or if the drive didn't send anything.
713 #
714 # Thanks to Andrew Ayers in the M100 group on Facebook for help finding the key trick.
715 #
716 # return value from "read" is crucial to distiguish a timeout from a null byte
717 # $?=0 = we read a non-null byte normally, $x contains a byte
718 # 1 = we read a null byte, $x is empty because we ate the null as a delimiter
719 # >128 = we timed out, $x is empty because there was no data, not even a null
720 tpdd_read () {
721 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
722 local -i i l=$1 ;local x ;rhex=() read_err=0
723 [[ "$2" ]] && tpdd_wait $2 $3
724 vecho 2 -n "$z: l=$l "
725 l=${1:-$TPDD_MAX_FILE_LENGTH}
726 for ((i=0;i<l;i++)) {
727 tpdd_wait
728 x=
729 IFS= read -d '' -r -t $read_timeout -n 1 -u 3 x ;read_err=$?
730 ((read_err==1)) && read_err=0
731 ((read_err)) && break
732 printf -v rhex[i] '%02X' "'$x"
733 vecho 2 -n "$i:${rhex[i]} "
734 }
735 ((v==9)) && { local c=$((10000+seq++)) ;x="${rhex[*]}" ;printf '%b' "x${x// /x}" >${0##*/}.$$.${c#?}.$z ; }
736 ((read_err>1)) && vecho 2 "read_err:$read_err" || vecho 2 ''
737 }
738
739 tpdd_read_unknown () {
740 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
741 local -i e= ;local x ;rhex=()
742 tpdd_wait_s
743 while : ;do
744 x=
745 IFS= read -d '' -r -t $read_timeout -n 1 -u 3 x ;e=$?
746 ((e==1)) && e=0
747 ((e)) && break
748 printf -v x '%02X' "'$x"
749 rhex+=($x)
750 done
751 ((v==9)) && { local c=$((10000+seq++)) ;x="${rhex[*]}" ;printf '%b' "x${x// /x}" >${0##*/}.$$.${c#?}.$z ; }
752 :
753 }
754
755 tpdd_read_BASIC () {
756 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
757 local x e ;printf -v e '%b' "x$BASIC_EOF"
758 tpdd_wait
759 while read -r -t 2 -u 3 x ;do
760 printf '%sn' "$x"
761 [[ "${x: -1}" == "$e" ]] && break
762 done
763 }
764
765 # check if data is available without consuming any
766 tpdd_check () {
767 local z=${FUNCNAME[0]} ;vecho 2 "$z($@)"
768 IFS= read -t 0 -u 3
769 }
770
771 # wait for data
772 # tpdd_wait timeout_ms busy_indication
773 # sleep() but periodically check the drive
774 # return once the drive starts sending data
775 tpdd_wait () {
776 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
777 local d=(: spin pbar) s
778 local -i i=-1 n p=$TPDD_WAIT_PERIOD_MS t=${1:-$TPDD_WAIT_TIMEOUT_MS} b=$2
779 ms_to_s $p ;s=$_s
780 ((t<p)) && t=p ;n=$(((t+50)/p))
781 ((b==1)) && spin +
782 until ((++i>n)) ;do
783 tpdd_check && break
784 ${d[b]} $i $n
785 _sleep $s
786 done
787 ((i>n)) && abrt "TIMED OUT"
788 ${d[b]} 1 1
789 ((b==1)) && spin -
790 ((b)) && echo
791 vecho 1 "$z: $@:$((i*p))"
792 }
793
794 tpdd_wait_s () {
795 until tpdd_check ;do _sleep 0.05 ;done
796 }
797
798 # Drain output from the drive to get in sync with it's input vs output.
799 tpdd_drain () {
800 local z=${FUNCNAME[0]} ;vecho 2 "$z($@)"
801 local x
802 while tpdd_check ;do
803 IFS= read -d '' -r -t $read_timeout -u 3 x
804 ((v>1)) && printf '%02X:%u:%sn' "'$x" "'$x" "$x"
805 done
806 }
807
808 ###############################################################################
809 # OPERATION MODE #
810 ###############################################################################
811 #
812 # operation-mode transaction format reference
813 #
814 # request block
815 #
816 # preamble 2 bytes 5A5A
817 # format 1 byte type of request block
818 # length 1 byte length of data in bytes
819 # data 0-128 bytes data
820 # checksum 1 byte 1's comp of LSByte of sum of format through data
821 #
822 # return block
823 #
824 # format 1 byte type of return block
825 # length 1 byte length of data in bytes
826 # data 0-128 bytes data
827 # checksum 1 byte 1's comp of LSByte of sum of format through data
828
829 ###############################################################################
830 # "Operation Mode" support functions
831
832 # calculate the checksum of $* (hex pairs)
833 # return in global $cksum (hex pair)
834 calc_cksum () {
835 local z=${FUNCNAME[0]} ;vecho 1 -n "$z($@):"
836 local -i s=0
837 while (($#)) ;do ((s+=16#$1)) ;shift ;done
838 ((s=(s&255)^255))
839 printf -v cksum '%02X' $s
840 vecho 1 "$cksum"
841 }
842
843 # verify the checksum of a received packet
844 # $* = data data data... csum (hex pairs)
845 verify_checksum () {
846 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
847 local -i l=$(($#-1)) ;local x= h=($*)
848 x=${h[l]} ;h[l]=
849 calc_cksum ${h[*]}
850 vecho 1 "$z: $x=$cksum"
851 ((16#$x==16#$cksum))
852 }
853
854 # check if a ret_std format response was ok (00) or error
855 ocmd_check_err () {
856 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
857 local -i e ;local x
858 vecho 1 "$z: ret_fmt=$ret_fmt ret_len=$ret_len ret_dat=(${ret_dat[*]}) read_err="$read_err""
859 ((${#ret_dat[*]}==1)) || { err_msg+=('Corrupt Response') ; ret_dat=() ;return 1 ; }
860 vecho 1 -n "$z: ${ret_dat[0]}:"
861 ((e=16#${ret_dat[0]}))
862 x='OK'
863 ((e)) && {
864 x='UNKNOWN ERROR'
865 ((${#opr_msg[${ret_dat[0]}]})) && x="${opr_msg[${ret_dat[0]}]}"
866 ret_err=${ret_dat[0]}
867 err_msg+=("$x")
868 }
869 vecho 1 "$x"
870 return $e
871 }
872
873 # build a valid operation-mode request block and send it to the tpdd
874 # 5A 5A format length data checksum
875 # fmt=$1 data=$2-*
876 ocmd_send_req () {
877 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
878 ((operation_mode)) || fcmd_mode 1
879 local fmt=$1 len ;shift
880 printf -v len '%02X' $#
881 calc_cksum $fmt $len $*
882 vecho 1 "$z: fmt="$fmt" len="$len" dat="$*" sum="$cksum""
883 tpdd_write 5A 5A $fmt $len $* $cksum
884 }
885
886 # read an operation-mode return block from the tpdd
887 # parse it into the parts: format, length, data, checksum
888 # verify the checksum
889 # return the globals ret_fmt, ret_len, ret_dat[], ret_sum
890 # $* is appended to the first tpdd_read() args (timeout_ms busy_indicator)
891 ocmd_read_ret () {
892 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
893 local -i t ;local l x ;ret_fmt= ret_len= ret_dat=() ret_sum=
894
895 vecho 1 "$z: reading 2 bytes (fmt len)"
896 tpdd_read 2 $* || return $?
897 vecho 1 "$z: (${rhex[*]})"
898 ((${#rhex[*]}==2)) || return 1
899 [[ "$ret_list" =~ |${rhex[0]}| ]] || abrt 'INVALID RESPONSE'
900 ret_fmt=${rhex[0]} ret_len=${rhex[1]}
901
902 ((l=16#${ret_len:-00}))
903 vecho 1 "$z: reading 0x$ret_len($l) bytes (data)"
904 tpdd_read $l || return $?
905 ((${#rhex[*]}==l)) || return 3
906 ret_dat=(${rhex[*]})
907 vecho 1 "$z: data=(${ret_dat[*]})"
908
909 vecho 1 "$z: reading 1 byte (checksum)"
910 tpdd_read 1 || return $?
911 ((${#rhex[*]}==1)) || return 4
912 ret_sum=${rhex[0]}
913 vecho 1 "$z: cksum=$ret_sum"
914
915 # compute the checksum and verify it matches the supplied checksum
916 verify_checksum $ret_fmt $ret_len ${ret_dat[*]} $ret_sum || abrt 'CHECKSUM FAILED'
917 }
918
919 # Space-pad or truncate $1 to 24 bytes.
920 # If in "Floppy Compatible" mode, and if the filename is already 6.2 or less,
921 # then also space-pad to %-6s.%-2s within that. Return in global tpdd_file_name
922 # normal : "hi.bat" -> "hi.bat "
923 # floppy_compat: "A.CO" -> "A .CO" -> "A .CO "
924 # floppy_compat: "Floppy_SYS" -> "Floppy_SYS "
925 mk_tpdd_file_name () {
926 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
927 local -i e ;local x t f="$1" ;tpdd_file_name=
928
929 $FLOPPY_COMPAT && {
930 t=${1%.*} x=${1##*.}
931 [[ "$1" =~ . ]] && ((${#t})) && ((${#t}<7)) && ((${#x}<3)) && printf -v f '%-6s.%-2s' "$t" "$x"
932 }
933
934 printf -v tpdd_file_name '%-24.24s' "$f"
935 }
936
937 # Un-pad a padded 6.2 on-disk filename to it's normal form.
938 # If in "Floppy Compatible" mode, and the filename would fit within 6.2, then
939 # collapse the internal spaces.
940 un_tpdd_file_name () {
941 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
942 local f= x= ;file_name="$1"
943
944 $FLOPPY_COMPAT && {
945 f=${file_name// /} ;x=${f##*.} ;f=${f%.*}
946 [[ "$1" =~ . ]] && ((${#f})) && ((${#f}<7)) && ((${#x}<3)) && printf -v file_name '%s.%s' "$f" "$x"
947 }
948 :
949 }
950
951 ###############################################################################
952 # "Operation Mode" drive functions
953 # wrappers for each "operation mode" function of the drive firmware
954
955 # directory entry
956 # fmt = 00
957 # len = 1a
958 # filename = 24 bytes
959 # attribute = "F" (always F for any file, null for unused entries)
960 # search form = 00=set_name | 01=get_first | 02=get_next
961 ocmd_dirent () {
962 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
963 local -i e w=10000 ;local r=${opr_fmt[req_dirent]} x f="$1" m=${3:-${dirent_cmd[get_first]}}
964 drive_err= file_name= file_attr= file_len= free_sectors=
965 ((operation_mode)) || fcmd_mode 1
966
967 # if tpdd2 bank 1, add 0x40 to opr_fmt[req]
968 ((bank)) && printf -v r '%02X' $((16#$r+16#40))
969
970 # construct the request
971 mk_tpdd_file_name "$f" # pad/truncate filename
972 str_to_shex "$tpdd_file_name" # filename (shex[0-23])
973 printf -v shex[24] '%02X' "'${2:-F}" # attribute - always "F"
974 printf -v shex[25] '%02X' $m # search form (set_name, get_first, get_next)
975
976 # send the request
977 ocmd_send_req $r ${shex[*]} || return $?
978
979 ((m==${dirent_cmd[get_first]})) && w=$LIST_WAIT_MS
980
981 # read the response
982 ocmd_read_ret $w || return $?
983
984 # check which kind of response we got
985 case "$ret_fmt" in
986 "${opr_fmt[ret_std]}") ocmd_check_err || return $? ;; # got a valid error return
987 "${opr_fmt[ret_dirent]}") : ;; # got a valid dirent return
988 *) abrt "$z: Unexpected Return" ;; # got no valid return
989 esac
990 ((${#ret_dat[*]}==28)) || abrt "$z: Got ${#ret_dat[*]} bytes, expected 28"
991
992 # parse a dirent return format
993 x="${ret_dat[*]:0:24}" ;printf -v file_name '%-24.24b' "x${x// /x}"
994 printf -v file_attr '%b' "x${ret_dat[24]}"
995 ((file_len=16#${ret_dat[25]}*256+16#${ret_dat[26]}))
996 ((free_sectors=16#${ret_dat[27]}))
997 vecho 1 "$z: mode=$m filename="$file_name" attr="$file_attr" len=$file_len free=$free_sectors"
998
999 # If doing set_name, and we got this far, then return success. Only the
1000 # caller knows if they expected file_name & file_attr to be null or not.
1001 ((m==${dirent_cmd[set_name]})) && return 0
1002
1003 # If doing get_first or get_next, filename[0]=00 means no more files.
1004 ((16#${ret_dat[0]}))
1005 }
1006
1007 # Get Drive Status
1008 # request: 5A 5A 07 00 ##
1009 # return : 07 01 ?? ##
1010 ocmd_status () {
1011 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1012 ((operation_mode)) || fcmd_mode 1
1013 ocmd_send_req ${opr_fmt[req_status]} || return $?
1014 ocmd_read_ret || return $?
1015 ocmd_check_err || return $?
1016 }
1017
1018 # Operation-Mode Format Disk
1019 #request: 5A 5A 06 00 ##
1020 #return : 12 01 ?? ##
1021 # "operation-mode" format is somehow special and different from FDC-mode format.
1022 # It creates 64-byte logical sectors, but if you use the FDC-mode format command
1023 # "ff 0" to format with 64-byte logical sectors, and then try save a file,
1024 # "ls" will show the file's contents were written into the directory sector.
1025 ocmd_format () {
1026 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1027 ((operation_mode)) || fcmd_mode 1
1028 local -i w=$FORMAT_WAIT_MS
1029 ((pdd2)) && {
1030 ((w+=FORMAT_TPDD2_EXTRA_WAIT_MS))
1031 echo "Formatting Disk, TPDD2 mode"
1032 } || {
1033 echo "Formatting Disk, TPDD1 "operation" mode"
1034 }
1035 ocmd_send_req ${opr_fmt[req_format]} || return $?
1036 ocmd_read_ret $w 2 || return $?
1037 ocmd_check_err || return $?
1038 }
1039
1040 # switch to FDC mode
1041 ocmd_fdc () {
1042 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1043 ((pdd2)) && abrt "$z requires TPDD1"
1044 ocmd_send_req ${opr_fmt[req_fdc]} || return $?
1045 operation_mode=0
1046 _sleep 0.1
1047 tpdd_drain
1048 }
1049
1050 # Open File
1051 # request: 5A 5A 01 01 MM ##
1052 # return : 12 01 ?? ##
1053 # MM = access mode: 01=write_new, 02=write_append, 03=read
1054 ocmd_open () {
1055 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1056 ((operation_mode)) || fcmd_mode 1
1057 local r=${opr_fmt[req_open]} m ;printf -v m '%02X' $1
1058 ((bank)) && printf -v r '%02X' $((16#$r+16#40))
1059 ocmd_send_req $r $m || return $? # open the file
1060 ocmd_read_ret || return $?
1061 ocmd_check_err
1062 }
1063
1064 # Close File
1065 # request: 5A 5A 02 00 ##
1066 # return : 12 01 ?? ##
1067 ocmd_close () {
1068 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1069 ((operation_mode)) || fcmd_mode 1
1070 local r=${opr_fmt[req_close]}
1071 ((bank)) && printf -v r '%02X' $((16#$r+16#40))
1072 ocmd_send_req $r
1073 ocmd_read_ret $CLOSE_WAIT_MS || return $?
1074 ocmd_check_err
1075 }
1076
1077 # Delete File
1078 # request: 5A 5A 05 00 ##
1079 # return : 12 01 ?? ##
1080 ocmd_delete () {
1081 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1082 ((operation_mode)) || fcmd_mode 1
1083 local r=${opr_fmt[req_delete]}
1084 ((bank)) && printf -v r '%02X' $((16#$r+16#40))
1085 ocmd_send_req $r || return $?
1086 ocmd_read_ret $DELETE_WAIT_MS 1 || return $?
1087 ocmd_check_err
1088 }
1089
1090 # Read File data
1091 # request: 5A 5A 03 00 ##
1092 # return : 10 00-80 1-128bytes ##
1093 ocmd_read () {
1094 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1095 ((operation_mode)) || fcmd_mode 1
1096 local r=${opr_fmt[req_read]}
1097 ((bank)) && printf -v r '%02X' $((16#$r+16#40))
1098 ocmd_send_req $r || return $?
1099 ocmd_read_ret || return $?
1100 vecho 1 "$z: ret_fmt=$ret_fmt ret_len=$ret_len ret_dat=(${ret_dat[*]}) read_err="$read_err""
1101
1102 # check if the response was an error
1103 case "$ret_fmt" in
1104 "${opr_fmt[ret_std]}") ocmd_check_err || return $? ;;
1105 "${opr_fmt[ret_read]}") ;;
1106 *) abrt "$z: Unexpected Response" ;;
1107 esac
1108
1109 # return true or not based on data or not
1110 # so we can do "while ocmd_read ;do ... ;done"
1111 ((${#ret_dat[*]}))
1112 }
1113
1114 # Write File Data
1115 # request: 5A 5A 04 ?? 1-128 bytes ##
1116 # return : 12 01 ?? ##
1117 ocmd_write () {
1118 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1119 ((operation_mode)) || fcmd_mode 1
1120 (($#)) || return 128
1121 local r=${opr_fmt[req_write]}
1122 ((bank)) && printf -v r '%02X' $((16#$r+16#40))
1123 ocmd_send_req $r $* || return $?
1124 tpdd_check || return 0
1125 ocmd_read_ret || return $?
1126 ocmd_check_err
1127 }
1128
1129 ###############################################################################
1130 # FDC MODE #
1131 ###############################################################################
1132 #
1133 # fdc-mode transaction format reference
1134 #
1135 # send: C [ ] [P[,P]...] CR
1136 #
1137 # C = command letter, ascii letter
1138 # optional space between command letter and first parameter
1139 # P = parameter (if any), integer decimal value in ascii numbers
1140 # ,p = more parameters if any, seperated by commas, ascii decimal numbers
1141 # CR = carriage return
1142 #
1143 # recv: 8 bytes as 4 ascii hex pairs representing 4 byte values
1144 #
1145 # 1st pair is the error status
1146 # remaining pairs meaning depends on the command
1147 #
1148 # Some fdc commands have another send-and-receive after that.
1149 # Receive the first response, if the status is not error, then:
1150 #
1151 # send: the data for a sector write
1152 # recv: another standard 8-byte response as above
1153 # or
1154 # send: single carriage-return
1155 # recv: data from a sector read
1156
1157 ###############################################################################
1158 # "FDC Mode" support functions
1159
1160 # read an FDC-mode 8-byte result block
1161 fcmd_read_result () {
1162 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1163 ((operation_mode)) && ocmd_fdc
1164 local -i i ;local x ;fdc_err= fdc_res= fdc_len= fdc_res_b=()
1165
1166 # read 8 bytes
1167 tpdd_read 8 $* || return $?
1168
1169 # This may look a little confusing because we end up un-hexing the same data
1170 # twice. tpdd_read() returns all data encoded as hex pairs, because that's
1171 # how we have to deal with binary data in shell variables. In this case the
1172 # original 8 bytes from the drive are themselves ascii hex pairs.
1173
1174 # re-constitute rhex[] from tpdd_read() back to the actual bytes sent
1175 # by the drive
1176 x="${rhex[*]}" ;printf -v x '%b' "x${x// /x}"
1177 vecho 1 "$z:$x"
1178
1179 # Decode the 8 bytes as
1180 # 2 bytes = hex pair representing an 8-bit integer error code
1181 # 2 bytes = hex pair representing an 8-bit integer result data
1182 # 4 bytes = 2 hex pairs representing a 16-bit integer length value
1183 ((fdc_err=16#${x:0:2})) # first 2 = status
1184 ((fdc_res=16#${x:2:2})) # next 2 = result
1185 ((fdc_len=16#${x:4:4})) # last 4 = length
1186
1187 # look up the status/error message for fdc_err
1188 x= ;[[ "${fdc_msg[fdc_err]}" ]] && x="${fdc_msg[fdc_err]}"
1189 ((fdc_err)) && err_msg+=("${x:-ERROR:${fdc_err}}")
1190
1191 # For some commands, fdc_res is actually 8 individual bit flags.
1192 # Provide the individual bits in fdc_res_b[] for convenience.
1193 fdc_res_b=()
1194 for ((i=7;i>=0;i--)) { fdc_res_b+=(${D2B[fdc_res]:i:1}) ; }
1195
1196 vecho 1 "$z: err:$fdc_err:"${fdc_msg[fdc_err]}" res:$fdc_res(${D2B[fdc_res]}) len:$fdc_len"
1197 }
1198
1199 ###############################################################################
1200 # "FDC Mode" drive functions
1201 # wrappers for each "FDC mode" function of the drive firmware
1202
1203 # select operation mode
1204 # fcmd_mode <0-1>
1205 # 0=fdc 1=operation
1206 fcmd_mode () {
1207 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1208 ((pdd2)) && abrt "$z requires TPDD1"
1209 (($#)) || return
1210 str_to_shex "${fdc_cmd[mode]}$1"
1211 tpdd_write ${shex[*]} 0D
1212 operation_mode=$1
1213 _sleep 0.1
1214 tpdd_drain
1215 }
1216
1217 # report drive condition
1218 fcmd_condition () {
1219 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1220 ((operation_mode)) && ocmd_fdc
1221 local x
1222 str_to_shex "${fdc_cmd[condition]}"
1223 tpdd_write ${shex[*]} 0D || return $?
1224
1225 fcmd_read_result || return $?
1226 ((fdc_err)) && return $fdc_err
1227
1228 # result bit 7 - disk not inserted
1229 x= ;((fdc_res_b[7])) && x=' Not'
1230 echo -n "Disk${x} Inserted"
1231 ((fdc_res_b[7])) && { echo ;return ; }
1232
1233 # result bit 5 - disk write-protected
1234 x='Writable' ;((fdc_res_b[5])) && x='Write-protected'
1235 echo ", $x"
1236 }
1237
1238 # FDC-mode format disk
1239 # fcmd_format [logical_sector_size_code]
1240 # size codes: 0=64 1=80 2=128 3=256 4=512 5=1024 6=1280 bytes
1241 # The drive firmware defaults to size code 3 when not given.
1242 # We intercept that and fill in our own default of 6 instead.
1243 fcmd_format () {
1244 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1245 ((operation_mode)) && ocmd_fdc
1246 typeset -i s=${1:-6}
1247 str_to_shex ${fdc_cmd[format]}$s
1248 echo "Formatting Disk with ${fdc_format_sector_size[s]:-""}-Byte Logical Sectors"
1249 tpdd_write ${shex[*]} 0D || return $?
1250 fcmd_read_result $FORMAT_WAIT_MS 2 || return $?
1251 ((fdc_err)) && err_msg+=(", Sector:$fdc_res")
1252 return $fdc_err
1253 }
1254
1255 # See the software manual page 11
1256 # The drive firmware function returns 13 bytes, only the 1st byte is used.
1257 #
1258 # 00 - current sector is not used by a file
1259 # ** - sector number of next sector in current file
1260 # FF - current sector is the last sector in current file
1261 #
1262 # read sector id section
1263 # fcmd_read_id physical_sector [quiet]
1264 fcmd_read_id () {
1265 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1266 ((operation_mode)) && ocmd_fdc
1267 str_to_shex "${fdc_cmd[read_id]}$1"
1268 tpdd_write ${shex[*]} 0D || return $?
1269 fcmd_read_result || { err_msg+=("err:$? res:"${fdc_res_b[*]}"") ;return $? ; }
1270 #vecho 2 "P:$1 LEN:${fdc_len} RES:${fdc_res}[${fdc_res_b[*]}]"
1271 ((fdc_err)) && { err_msg+=("err:$fdc_err res:"${fdc_res_b[*]}"") ;return $fdc_err ; }
1272 tpdd_write 0D || return $?
1273 tpdd_read $PDD1_SECTOR_ID_LENGTH || return $?
1274 ((${#rhex[*]}<PDD1_SECTOR_ID_LENGTH)) && { err_msg+=("Got ${#rhex[*]} of $PDD1_SECTOR_ID_LENGTH bytes") ; return 1 ; }
1275 ((${#2})) || printf "I %02u %04u %sn" "$1" "$fdc_len" "${rhex[*]}"
1276 }
1277
1278 # read a logical sector
1279 # fcmd_read_logical physical logical [quiet]
1280 # physical=0-79 logical=1-20
1281 fcmd_read_logical () {
1282 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1283 ((operation_mode)) && ocmd_fdc
1284 local -i ps=$1 ls=${2:-1} || return $? ;local x
1285 str_to_shex "${fdc_cmd[read_sector]}$ps,$ls"
1286 tpdd_write ${shex[*]} 0D || return $?
1287 fcmd_read_result || { err_msg+=("err:$? res[${fdc_res_b[*]}]") ;return $? ; }
1288 ((fdc_err)) && { err_msg+=("err:$fdc_err res[${fdc_res_b[*]}]") ;return $fdc_err ; }
1289 ((fdc_res==ps)) || { err_msg+=("Unexpected Physical Sector "$ps" Returned") ;return 1 ; }
1290 tpdd_write 0D || return $?
1291 # The drive will appear ready with data right away, but if you read too soon
1292 # the data will be corrupt or incomplete.
1293 # Take 2/3 of the number of bytes we expect to read, and sleep that many MS.
1294 ms_to_s $(((fdc_len/3)*2)) ;_sleep $_s
1295 tpdd_read $fdc_len || return $?
1296 ((${#rhex[*]}<fdc_len)) && { err_msg+=("Got ${#rhex[*]} of $fdc_len bytes") ; return 1 ; }
1297 ((${#3})) || printf "S %02u %02u %04u %sn" "$ps" "$ls" "$fdc_len" "${rhex[*]}"
1298 }
1299
1300 # write a physical sector ID section
1301 # fcmd_write_id physical data(hex pairs)
1302 fcmd_write_id () {
1303 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1304 ((operation_mode)) && ocmd_fdc
1305 local -i p=$((10#$1)) ;shift
1306 str_to_shex "${fdc_cmd[write_id]}$p"
1307 tpdd_write ${shex[*]} 0D || return $?
1308 fcmd_read_result || { err_msg+=("err:$? res[${fdc_res_b[*]}]") ;return $? ; }
1309 ((fdc_err)) && { err_msg+=("err:$fdc_err res[${fdc_res_b[*]}]") ;return $fdc_err ; }
1310 shift ; # discard the size field
1311 tpdd_write $* || return $?
1312 fcmd_read_result || { err_msg+=("err:$? res[${fdc_res_b[*]}]") ;return $? ; }
1313 ((fdc_err)) && { err_msg+=("err:$fdc_err res[${fdc_res_b[*]}]") ;return $fdc_err ; }
1314 :
1315 }
1316
1317 # write a logical sector
1318 # fcmd_write_logical physical logical data(hex pairs)
1319 fcmd_write_logical () {
1320 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1321 ((operation_mode)) && ocmd_fdc
1322 local -i ps=$((10#$1)) ls=$((10#$2)) ;shift 2
1323 str_to_shex "${fdc_cmd[write_sector]}$ps,$ls"
1324 tpdd_write ${shex[*]} 0D || return $?
1325 fcmd_read_result || { err_msg+=("err:$? res[${fdc_res_b[*]}]") ;return $? ; }
1326 ((fdc_err)) && { err_msg+=("err:$fdc_err res[${fdc_res_b[*]}]") ;return $fdc_err ; }
1327 tpdd_write $* || return $?
1328 fcmd_read_result || { err_msg+=("err:$? res[${fdc_res_b[*]}]") ;return $? ; }
1329 ((fdc_err)) && { err_msg+=("err:$fdc_err res[${fdc_res_b[*]}]") ;return $fdc_err ; }
1330 :
1331 }
1332
1333 ###############################################################################
1334 # Local Commands
1335 # high level functions implemented here in the client
1336
1337 # list disk directory
1338 lcmd_ls () {
1339 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1340 local -i m=${dirent_cmd[get_first]}
1341
1342 ((pdd2)) && {
1343 echo "--- Directory Listing [$bank] ---"
1344 } || {
1345 echo '------ Directory Listing ------'
1346 }
1347 while ocmd_dirent '' '' $m ;do
1348 un_tpdd_file_name "$file_name"
1349 printf '%-24.24b %6un' "$file_name" "$file_len"
1350 ((m==${dirent_cmd[get_first]})) && m=${dirent_cmd[get_next]}
1351 done
1352 echo '-------------------------------'
1353 echo "$((free_sectors*PHYSICAL_SECTOR_LENGTH)) bytes free"
1354 }
1355
1356 # load a file (copy a file from tpdd to local file or memory)
1357 # lcmd_load source_filename [destination_filename]
1358 # If $2 is set but empty, read into global fhex[] instead of writing a file
1359 lcmd_load () {
1360 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1361 local x s=$1 d=${2:-$1} ;local -i p= l= ;fhex=()
1362 (($#)) || return 2
1363 (($#==2)) && ((${#2}==0)) && d= # read into memory instead of file
1364 x= ;((pdd2)) && x="[$bank]"
1365 echo -n "Loading TPDD$x:$s" ;unset x
1366 ocmd_dirent "$s" '' ${dirent_cmd[set_name]} || return $? # set the source filename
1367 ((${#file_name})) || { err_msg+=('No Such File') ; return 1 ; }
1368 l=$file_len # file size provided by dirent()
1369 ((${#d})) && {
1370 echo " to $d"
1371 [[ -e "$d" ]] && { err_msg+=('File Exists') ;return 1 ; }
1372 > $d
1373 } || {
1374 echo
1375 }
1376 pbar 0 $l 'bytes'
1377 ocmd_open ${open_mode[read]} || return $? # open the source file for reading
1378 while ocmd_read ;do # read a block of data from tpdd
1379 ((${#d})) && {
1380 x="${ret_dat[*]}" ;printf '%b' "x${x// /x}" >> "$d" # add to file
1381 } || {
1382 fhex+=(${ret_dat[*]}) # add to fhex[]
1383 }
1384 ((p+=${#ret_dat[*]})) ;pbar $p $l 'bytes'
1385 ((${#ret_dat[*]}<128)) && break # stop if we get less than 128 bytes
1386 ((p>=l)) && break # stop if we reach the expected total
1387 done
1388 ((ret_err==30)) && ((p%128==0)) && unset ret_err err_msg # special case
1389 ocmd_close || return $? # close the source file
1390 ((p==l)) || { err_msg+=('Error') ; return 1 ; }
1391 echo
1392 }
1393
1394 # save a file (copy a file from local file or memory to tpdd)
1395 # lcmd_save source_filename [destination_filename]
1396 # If $1 is set but empty, get data from global fhex[] instead of reading a file
1397 lcmd_save () {
1398 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1399 local -i n p l ;local s=$1 d=${2:-$1} x
1400 (($#)) || return 2
1401 (($#==2)) && ((${#1}==0)) && s= # read from memory instead of file
1402 ((${#d})) || return 3
1403 ((${#s})) && {
1404 [[ -r "$s" ]] || { err_msg+=(""$s" not found") ;return 1 ; }
1405 file_to_fhex "$s" || return 4
1406 }
1407 l=${#fhex[*]}
1408 x= ;((pdd2)) && x="[$bank]"
1409 echo "Saving TPDD$x:$d" ;unset x
1410 ocmd_dirent "$d" '' ${dirent_cmd[set_name]} || return $?
1411 ((${#file_name})) && { err_msg+=('File Exists') ; return 1 ; }
1412 ocmd_open ${open_mode[write_new]} || return $?
1413 for ((p=0;p<l;p+=128)) {
1414 pbar $p $l 'bytes'
1415 ocmd_write ${fhex[*]:p:128} || return $?
1416 tpdd_wait
1417 ocmd_read_ret || return $?
1418 ocmd_check_err || return $?
1419 }
1420 pbar $l $l 'bytes'
1421 echo
1422 ocmd_close || return $?
1423 }
1424
1425 # delete one or more files
1426 # lcmd_rm filename [filenames...]
1427 lcmd_rm () {
1428 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1429 (($#)) || return 1
1430 local f
1431 for f in $* ;do
1432 echo -n "Deleting TPDD:$f "
1433 ocmd_dirent "$f" '' ${dirent_cmd[set_name]} || return $?
1434 ((${#file_name})) || { err_msg+=('No Such File') ; return 1 ; }
1435 ocmd_delete || return $?
1436 printf 'r%79srDeleted TPDD:%sn' '' "$f"
1437 done
1438 }
1439
1440 # currently, mv and cp are mostly redundant, but later mv may be based on
1441 # fdc-mode commands to edit sector 0 instead of load-delete-save
1442 #
1443 # load-rm-save is less safe than load-save-rm, but works when the disk is full
1444 lcmd_mv () {
1445 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1446 (($#==2)) || return 1
1447 lcmd_load "$1" '' && lcmd_rm "$1" && lcmd_save '' "$2"
1448 }
1449
1450 lcmd_cp () {
1451 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1452 (($#==2)) || return 1
1453 lcmd_load "$1" '' && lcmd_save '' "$2"
1454 }
1455
1456 # read ID and all logical sectors in a physical sector
1457 # lcmd_read_physical physical [quiet]
1458 pdd1_read_physical () {
1459 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1460 local -i p=$1 l t || return $? ;local u= h=()
1461
1462 # read the logical sectors
1463 fcmd_read_logical $p 1 $2 || return $?
1464 h+=(${rhex[*]})
1465 ((t=PHYSICAL_SECTOR_LENGTH/fdc_len))
1466 for ((l=2;l<=t;l++)) {
1467 ((${#2})) && pbar $p $PHYSICAL_SECTOR_COUNT "P:$p L:$l"
1468 fcmd_read_logical $p $l $2 || return $?
1469 h+=(${rhex[*]})
1470 }
1471 rhex=(${h[*]})
1472 }
1473
1474 # read all physical sectors on the disk
1475 pdd1_dump_disk () {
1476 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1477 local -i p t=$PHYSICAL_SECTOR_COUNT m=1 n ;local f=$1
1478 ((${#f})) && {
1479 echo "Dumping Disk to File: "$f""
1480 [[ -e "$f" ]] && { err_msg+=('File Exists') ;return 1 ; }
1481 pbar 0 $t "P:- L:-"
1482 >$f
1483 }
1484
1485 for ((p=0;p<t;p++)) {
1486 # read the ID section
1487 fcmd_read_id $p $f || return $?
1488 ((n=PHYSICAL_SECTOR_LENGTH/fdc_len))
1489 ((${#f})) && printf '%02u %04u %s ' "$p" "$fdc_len" "${rhex[*]}" >> $f
1490
1491 # read the physical sector
1492 pdd1_read_physical $p $m || return $?
1493 ((${#f})) && {
1494 printf '%sn' "${rhex[*]}" >> $f
1495 pbar $((p+1)) $t "P:$p L:$n"
1496 }
1497 }
1498 ((${#f})) && echo
1499 }
1500
1501 # read a pdd1 hex dump file and write the contents back to a disk
1502 # pdd1_hex_file_to_disk filename
1503 pdd1_restore_disk () {
1504 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1505 local d r s x ;local -i i t sz= n
1506
1507 # Read the dump file into d[]
1508 exec 5<"$1" || return $?
1509 mapfile -u 5 d || return $?
1510 exec 5<&-
1511
1512 # Get the logical sector size, abort if not uniform.
1513 t=${#d[*]}
1514 for ((i=0;i<t;i++)) {
1515 r=(${d[i]})
1516 ((sz)) || sz=$((10#${r[1]}))
1517 ((sz==$((10#${r[1]})))) || { err_msg+=('Mixed Logical Sector Sizes') ; return 1 ; }
1518 }
1519
1520 # FDC-mode format the disk with the appropriate sector size.
1521 for i in ${!fdc_format_sector_size[*]} 9999 ;do ((${fdc_format_sector_size[i]}==sz)) && break ;done
1522 ((i==9999)) && { err_msg+=('Unrecognized Logical Sector Size') ; return 1 ; }
1523 ((n=PHYSICAL_SECTOR_LENGTH/sz))
1524 fcmd_format $i
1525
1526 # Write the sectors, skip un-used sectors
1527 echo "Restoring Disk from $1"
1528 for ((i=0;i<t;i++)) {
1529 r=(${d[i]})
1530 pbar $((i+1)) $t "P:${r[0]} L:-/-"
1531 x=${r[@]:2} ;x=${x//[ 0]/} ;((${#x})) || continue
1532
1533 # write the ID section
1534 fcmd_write_id ${r[@]:0:$((2+PDD1_SECTOR_ID_LENGTH))} || return $?
1535
1536 # logical sectors count from 1
1537 for ((l=1;l<=n;l++)) {
1538 pbar $((i+1)) $t "P:${r[0]} L:$l/$n"
1539 s=(${r[@]:$((2+PDD1_SECTOR_ID_LENGTH+(sz*(l-1)))):sz})
1540 x=${s[@]:2} ;x=${x//[ 0]/} ;((${#x})) || continue
1541 fcmd_write_logical ${r[0]} $l ${s[*]} || return $?
1542 }
1543 }
1544 echo
1545 }
1546
1547 ###############################################################################
1548 # manual/raw debug commands
1549
1550 lcmd_com_test () {
1551 test_com && echo 'com is open' || echo 'com is closed'
1552 }
1553
1554 lcmd_com_open () {
1555 open_com
1556 lcmd_com_test
1557 }
1558
1559 lcmd_com_close () {
1560 close_com
1561 lcmd_com_test
1562 }
1563
1564 # read the Space Managment Table
1565 # track 0 sector 0, bytes 1240-1259
1566 read_smt () {
1567 ((pdd2)) && {
1568 pdd2_sector_cache 0 $bank 0 || return $?
1569 pdd2_read_cache 0 $SMT_OFFSET $SMT_LENGTH >/dev/null || return $?
1570 echo "SMT $bank: ${ret_dat[*]:3}"
1571 } || {
1572 pdd1_read_physical 0 >/dev/null || return $?
1573 echo "SMT: ${rhex[*]:$SMT_OFFSET:$SMT_LENGTH}"
1574 }
1575 }
1576
1577 ###############################################################################
1578 # TPDD2
1579
1580 # TPDD2 Get Drive Status
1581 # request: 5A 5A 0C 00 ##
1582 # return : 15 01 ?? ##
1583 pdd2_condition () {
1584 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1585 ((pdd2)) || abrt "$z requires TPDD2"
1586 local -i i ;local x
1587 ocmd_send_req ${opr_fmt[req_condition]} || return $?
1588 ocmd_read_ret || return $?
1589
1590 # response data is a single byte wih 4 bit-flags
1591 ocmd_cond_b=()
1592 for ((i=7;i>=0;i--)) { ocmd_cond_b+=(${D2B[16#${ret_dat[0]}]:i:1}) ; }
1593
1594 # bit 2 - disk inserted
1595 x= ;((ocmd_cond_b[2])) && {
1596 x='Not Inserted'
1597 } || {
1598 # bit 3 - disk changed
1599 x= ;((ocmd_cond_b[3])) && x='Changed ,'
1600 # bit 1 - disk write-protected
1601 ((ocmd_cond_b[1])) && x+='Write-protected' || x+='Writable'
1602 }
1603 echo "Disk $x"
1604 # result bit 0 - power
1605 x='Normal' ;((ocmd_cond_b[0])) && x='Low'
1606 echo "Power $x"
1607 }
1608
1609 # TPDD2 read from cache
1610 # pdd2_read_cache mode offset length [filename]
1611 pdd2_read_cache () {
1612 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1613 ((pdd2)) || abrt "$z requires TPDD2"
1614 local x
1615
1616 # 4-byte request data
1617 # 00 mode
1618 # 0000-04C0 offset
1619 # 00-FC length
1620 printf -v x '%02X %02X %02X %02X' $1 $(($2/256)) $(($2%256)) $3
1621
1622 ocmd_send_req ${opr_fmt[req_read_cache]} $x || return $?
1623 ocmd_read_ret || return $?
1624
1625 # returned data:
1626 # [0] mode
1627 # [1][2] offset
1628 # [3]+ data
1629 ((${#4})) && {
1630 printf '%02X %02X %sn' "$track_num" "$sector_num" "${ret_dat[*]}" >>$4
1631 } || {
1632 printf 'T:%02u S:%u m:%u O:%05u %sn' "$track_num" "$sector_num" "$((16#${ret_dat[0]}))" "$((16#${ret_dat[1]}${ret_dat[2]}))" "${ret_dat[*]:3}"
1633 #printf -v x '%s' "${ret_dat[*]:3}" ;printf '%bn' "x${x// /x}"
1634 }
1635 }
1636
1637 # TPDD2 write to cache
1638 # pdd2_write_cache mode offset_msb offset_lsb data...
1639 pdd2_write_cache () {
1640 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1641 ((pdd2)) || abrt "$z requires TPDD2"
1642 ocmd_send_req ${opr_fmt[req_write_cache]} $* || return $?
1643 ocmd_read_ret || return $?
1644 ocmd_check_err
1645 }
1646
1647 # TPDD2 write to cache, decimal offset for cli convenience
1648 # lcmd_write_cache mode offset data...
1649 lcmd_write_cache () {
1650 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1651 ((pdd2)) || abrt "$z requires TPDD2"
1652 local x
1653
1654 # payload header
1655 # 00|01 mode
1656 # 0000-0500 offset
1657 printf -v x '%02X %02X %02X' $1 $((10#$2/256)) $((10#$2%256)) ;shift 2
1658
1659 pdd2_write_cache $x $* || return $?
1660 }
1661
1662 # TPDD2 copy sector between disk and cache
1663 # pdd2_cache_sector track sector mode
1664 # pdd2_cache_sector 0-79 0-1 0|2
1665 # mode: 0=disk-to-cache 2=cache-to-disk
1666 pdd2_sector_cache () {
1667 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1668 ((pdd2)) || abrt "$z requires TPDD2"
1669 local x m=${3:-0} ;track_num=$1 sector_num=$2
1670
1671 # 5-byte request data
1672 # 00|02 mode
1673 # 00 unknown
1674 # 00-4F track#
1675 # 00 unknown
1676 # 00-01 sector
1677 printf -v x '%02X 00 %02X 00 %02X' $m $track_num $sector_num
1678
1679 ocmd_send_req ${opr_fmt[req_sector_cache]} $x || return $?
1680 ocmd_read_ret || return $?
1681 ocmd_check_err
1682 }
1683
1684 # TPDD2 dump disk
1685 # pdd2_dump_disk [filename]
1686 pdd2_dump_disk () {
1687 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1688 ((pdd2)) || abrt "$z requires TPDD2"
1689 local -i t s f fq tb b=
1690 ((fq=PHYSICAL_SECTOR_LENGTH/PDD2_SECTOR_CHUNK_LENGTH))
1691 ((tb=fq*PHYSICAL_SECTOR_COUNT*2*PDD2_SECTOR_CHUNK_LENGTH))
1692 ((${#1})) && {
1693 printf 'Dumping Disk to File: "%s"n' "$1"
1694 pbar 0 $tb bytes
1695 >$1
1696 }
1697
1698 for ((t=0;t<PHYSICAL_SECTOR_COUNT;t++)) {
1699 for ((s=0;s<2;s++)) {
1700 pdd2_sector_cache $t $s 0 || return $?
1701 pdd2_read_cache 1 32772 4 $1 || return $? # metadata
1702 for ((f=0;f<fq;f++)) {
1703 pdd2_read_cache 0 $((PDD2_SECTOR_CHUNK_LENGTH*f)) $PDD2_SECTOR_CHUNK_LENGTH $1 || return $?
1704 pbar $((b+=PDD2_SECTOR_CHUNK_LENGTH)) $tb bytes
1705 }
1706 }
1707 }
1708
1709 ((${#1})) && echo
1710 }
1711
1712 pdd2_flush_cache () {
1713 # mystery metadata writes
1714 pdd2_write_cache 01 00 83 || return $?
1715 pdd2_write_cache 01 00 96 || return $?
1716 # flush the cache to disk
1717 pdd2_sector_cache $1 $2 2 || return $?
1718 }
1719
1720 pdd2_restore_disk () {
1721 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1722 ((pdd2)) || abrt "$z requires TPDD2"
1723 local d r ;local -i i n t s m b= tb=$((PHYSICAL_SECTOR_LENGTH*PHYSICAL_SECTOR_COUNT*2)) ;track_num= sector_num=
1724
1725 # Format the disk
1726 ocmd_format || return $?
1727
1728 # Read the dump file into d[]
1729 exec 5<"$1" || return $?
1730 mapfile -u 5 d || return $?
1731 exec 5<&-
1732 n=${#d[@]}
1733
1734 # Write the sectors
1735 # each record in the file is:
1736 # track sector mode offset_msb offset_lsb data...
1737 echo "Restoring Disk from $1"
1738 for ((i=0;i<n;i++)) {
1739 r=(${d[i]})
1740 vecho 2 "${r[*]}"
1741
1742 t=16#${r[0]} s=16#${r[1]} m=16#${r[2]}
1743
1744 # encountered new sector in file, write cache to disk
1745 ((t==track_num)) && ((s==sector_num)) || {
1746 ((i)) && { pdd2_flush_cache $track_num $sector_num || return $? ; }
1747 track_num=$t sector_num=$s
1748 }
1749
1750 ((m)) || pbar $((b+=${#r[*]}-5)) $tb 'bytes'
1751
1752 # write to cache
1753 pdd2_write_cache ${r[*]:2} || return $?
1754 }
1755 pdd2_flush_cache $track_num $sector_num || return $?
1756 echo
1757 }
1758
1759 ###############################################################################
1760 # Server Functions
1761 # These functions are for talking to a client not a drive
1762
1763 # write $* to com port with per-character delay
1764 # followed by the BASIC EOF character
1765 srv_send_loader () {
1766 local z=${FUNCNAME[0]} ;vecho 1 "$z($@)"
1767 local -i i l ;local s REPLY
1768 ms_to_s $LOADER_PER_CHAR_MS ;s=$_s
1769 file_to_fhex $1
1770 fhex+=('0D' $BASIC_EOF)
1771
1772 echo "Installing $1"
1773 echo 'Prepare the portable to receive. Hints:'
1774 echo -e "tRUN "COM:98N1ENN"t(for TANDY, Kyotronic, Olivetti)"
1775 echo -e "tRUN "COM:9N81XN"t(for NEC)n"
1776 read -p 'Press [Enter] when ready...'
1777
1778 l=${#fhex[*]}
1779 for ((i=0;i<l;i++)) {
1780 printf '%b' "x${fhex[i]}" >&3
1781 pbar $((i+1)) $l 'bytes'
1782 _sleep $s
1783 }
1784 echo
1785 }
1786
1787 ###############################################################################
1788 # Main
1789 typeset -a err_msg=() shex=() fhex=() rhex=() ret_dat=() fdc_res_b=()
1790 typeset -i _y= pdd2=$((TPDD_MODEL-1)) bank= operation_mode=1 read_err= fdc_err= fdc_res= fdc_len= track_num= sector_num= _om=99
1791 cksum=00 ret_err= ret_fmt= ret_len= ret_sum= tpdd_file_name= file_name= ret_list='|' _s=
1792 readonly LANG=C D2B=({0,1}{0,1}{0,1}{0,1}{0,1}{0,1}{0,1}{0,1})
1793 ((v==9)) && typeset -i seq=0
1794 ms_to_s $TTY_READ_TIMEOUT_MS ;read_timeout=$_s
1795 [[ "$0" =~ .*pdd2(.sh)?$ ]] && pdd2=1 operation_mode=2
1796 for x in ${!opr_fmt[*]} ;do [[ "$x" =~ ^ret_.* ]] && ret_list+="${opr_fmt[$x]}|" ;done
1797 unset x
1798
1799 # for _sleep()
1800 readonly sleep_fifo="/tmp/.${0////_}.sleep.fifo"
1801 [[ -p $sleep_fifo ]] || mkfifo "$sleep_fifo" || abrt "Error creating sleep fifo "$sleep_fifo""
1802 exec 4<>$sleep_fifo
1803
1804 # tpdd serial port
1805 for PORT in $1 /dev/$1 ;do [[ -c "$PORT" ]] && break || PORT= ;done
1806 [[ "$PORT" ]] && shift || get_tpdd_port
1807 vecho 1 "Using port "$PORT""
1808 open_com || exit $?
1809
1810 # non-interactive mode
1811 (($#)) && { do_cmd "$@" ;exit $? ; }
1812
1813 # interactive mode
1814 while read -p"TPDD(${mode[operation_mode]})> " __c ;do do_cmd "${__c}" ;done