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