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' "'$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 |