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 |

