Jay Taylor's notes
back to listing indexpdd.sh/pdd.sh at main · bkw777/pdd.sh
[web search]
Original source (github.com)
Clipped on: 2022-02-15
Skip to content
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 |