Ok here is a break down of a transaction to read a single logical sector.
It's pdd.sh with debug level 4, interspersed with explanations
Below, to cut through everything, all the actual serial port traffic is
just the tpdd_read() and tpdd_write() commands.
Everything else is just the stuff to arrive at what to send or how to
interpret what you received.
But every byte that is sent to the drive is sent by tpdd_write(bytes)
And every byte that is read from the drive is read by tpdd_read(# of bytes)
tpdd_write(5A 5A 08 00 F7) means to send exactly and only those 5 bytes
0x5A 0x5A 0x08 0x00 0xF7
tpdd_read(2) means to read exactly 2 bytes
---------------------------------------------------------------
## launch with DEBUG=4 via environment variable so that it's already in
effect even before the script gets a chance to parse the command line
bkw@fw:~$ DEBUG=4 pdd
## figure out the tty and set up the serial port settings
get_tpdd_port()
Using port "/dev/ttyUSB0"
open_com()
set_stty()
speed 19200 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt
= ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 1;
-parenb -parodd -cmspar cs8 hupcl -cstopb cread clocal crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl
-ixon -ixoff
-iuclc -ixany -imaxbel -iutf8
-opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0
vt0 ff0
-isig -icanon iexten -echo echoe echok -echonl -noflsh -xcase -tostop
-echoprt
echoctl echoke -flusho -extproc
## running just "pdd" with no commandline args tells the script to
present a prompt for interactive commands
## This prompt shows the state of the client, what mode the client is in
and what it thinks the drive is in
## opr = TPDD1 in "operation mode"
## 6.2 = write and expect 6.2 filenames on the drive filename
(Floppy/TS-DOS compatible)
## F = write and expect "F" for the attribute byte on the drive filename
(Floppy/TS-DOS compatible)
##
## The command "rl 2 5" means:
## read logical - read a single logical sector
## physical sector 2 (counts from 0 to 79)
## logical sector 5 (counts from 1 to 1, up to 1 to 20, depending on the
logical sector size, which is determined when the disk is formatted)
## The Sardine dictionary disk uses 128byte sectors, so for that disk,
the valid values for logical sector are 1-10
##
## The script does some stuff automatically to put the drive into the
necessary mode for the requested command, so although the command above
is read-logical 2,5, it will do a bunch of other stuff first now
PDD(opr:6.2,F)> rl 2 5
do_cmd(rl 2 5)
_init()
## fonzie_smack is what I named a sort of joggle routine similar to what
TS-DOS does on start-up.
## you basically never know what state the drive is in, yet, you always
need to know that, and there is no really good way to query the drive
"what state are you in?" nor is there a really good way to tell the
drive "Whatever state you're in right now, reset and go into state foo"
## The closest is this A>B>A cycle that TS-DOS does where it blindly
tells the drive to go to OPR mode, then to FDC mode, then back to OPR
mode. Assuming the drive isn't locked up or in the middle of some
transaction, then this usually leaves the drive in OPR mode.
fonzie_smack()
tpdd_drain()
tpdd_check()
tpdd_write(4D 31 0D)
tpdd_drain()
tpdd_check()
tpdd_write(5A 5A 08 00 F7)
tpdd_drain()
tpdd_check()
tpdd_write(4D 31 0D)
tpdd_drain()
tpdd_check()
## unk23 is what the old dlplus called "ts-dos mystery command"
## it's unknown what it *really* means, but it's a reliable way to tell
the difference between TPDD1 and TPDD2 without locking up the drive by
giving an invalid command to the wrong drive.
## when you issue a certain request, the real drive gives back this
response, and it's always the same response for all TPDD1 drives.
## so if you issue the request and get the expected response, the drive
is a TPDD1, otherwise it's a TPDD2, and in either case it doesn't lock
up the drive.
ocmd_pdd2_unk23()
ocmd_send_req(23)
calc_cksum(23 00):DC
ocmd_send_req: fmt="23" len="00" dat="" chk="DC"
tpdd_write(5A 5A 23 00 DC)
ocmd_read_ret()
ocmd_read_ret: reading 2 bytes (fmt len)
tpdd_read(2)
tpdd_wait()
tpdd_check()
tpdd_check()
Detected TPDD1
## Now we've ensured the drive is in a known state and detected the type
of drive, so we know we have a TPDD1 and it;s in OPR mode ready for a
TPDD1 OPR command.
## start the read-logical command
fcmd_read_logical(2 5)
## firs step, since read-logical is an FDC command, switch the drive to
FDC mode
ocmd_fdc()
## the command for FDC mode is 0x08
ocmd_send_req(08)
## the oxo8 command has no payload, so, payload length 0
## calculate the checksum for two bytes: 0x08 0x00
calc_cksum(08 00):F7
## assemble the tpdd packet from preamble to checksum and send it
## preamble is the same for all packats: 0x5A 0x5A (aka "ZZ")
## command: 0x08
## payload/data length: 0 -> 0x00
## ones-compliment checksum for all the bytes after the ZZ and before
the checksum itself -> 0x08 0x00 -> 0xF7
## (sum all the bytes, then invert all the bits in that sum)
## the total packet ends up: 0x5A 0x5A 0x08 0x00 0xF7
## For OPR commands, there are no other bytes, no trailing linefeed or
carriage return or null
ocmd_send_req: fmt="08" len="00" dat="" chk="F7"
tpdd_write(5A 5A 08 00 F7)
## read and discard any possible junk on the line
## the 0x08 command does not return a response, there should not be
anything to read
## this is just to ensure the client & server are both ready, it
shouldn't technically be needed
tpdd_drain()
tpdd_check()
## finally actually construct the command packet for read-logical 2,5
and send it
## the real-logical command is "R", optional single space characater,
and arguments physical#,logical#
## So the command for logical#5 within physical#2, is "R2,5"
## Unlike OPR commands, FDC commands are terminated by a trailing 0x0D,
and there's no checksum
str_to_shex(R2,5)
shex[] 52 32 2C 35
tpdd_write(52 32 2C 35 0D)
## try to read a standard FDC-mode retrun packet, allowing up to 5000 ms
before giving up
fcmd_read_ret(5000)
## FDC-mode return packets have a standard size which is 8 bytes
## try to read 8 bytes
tpdd_read(8 5000)
tpdd_wait(5000)
tpdd_check()
[...] # repeats...
tpdd_check()
tpdd_wait: 5000:1400
tpdd_read: l=8
tpdd_wait()
tpdd_check()
tpdd_wait: :0
[...]
tpdd_check()
tpdd_wait: :0
## got 8 bytes
30 30 30 32 30 31 30 30
## interpret the 8 bytes
fcmd_read_ret: 00020100
## the response may be an error code, or if not an error then will
contain the logical sector size code
## you need that to know how many bytes to expect next (you don't just
know the logical sector size arbitrarily,
## you read the value from this initial response to the R command
## The first 2 bytes are an ascii hex pair that you convert into an
8-bit int, and that is an error or status code
## The 2nd 2 bytes are another ascii hex pair that you convert into an
8bit int, and that is error/result data
## the last 4 bytes are 2 ascii hex pairs, that you convert into a 16bit
int, and this has different meanings depending on the command, but is
usually a length or an address or an offset.
## 0x30 0x30 is ascii 00, meaning 0x00, meaning the integer value 0,
meaning status/error 0, meaning "OK / success"
## 0x30 0x32 is ascii 02, meaning 0x02, meaning the integer value 2,
meaning (in THIS case) physical sector 2
## 0x30 0x31 0x30 0x30 is ascii 0100, meaning 0x0100, meaning the
integer value 256, meaning (in THIS case) to read 256 bytes next.
##
## this is a little confusing in the context of this shell script,
because the script also encodes every single byte from the drive into hex,
## while these particular 8 bytes are actually themselves ascii hex from
the drive,
## so they end up getting double-converted and it's confusing if you're
reading the shell code and trying to follow it.
fcmd_read_ret: err:0="OK" res:2 len:256
## if the response code was not 0, that would have been an error so we'd
have to abort here
## if the response data had not matched the "2" we requested, that would
have been an error, so abort in that case too
## if those two conditions pass, then send a single 0x0D to the drive to
"ack" the response and tell the drive you are ready to receive the data.
tpdd_write(0D)
## try to read 256 bytes (again it's only 256 in this case because the
response above said to expect 256 bytes)
## don't try to read *immediately*
## this is crazy but, if you try to read too fast, the tty will say
there is data ready to read, but if you try to actually read as soon as
the tty says there is data available, you'll lock up the drive.
## Instead, first sleep a small delay based off the number of expected
bytes. take 2/3 of the number of bytes and wait that many ms
## it's not shown in the debug output because it's not the argument to
any commands, there is just a sleep command in the code at this point
## ms_to_s $(((fdc_len/3)*2)) ;_sleep $_s
##
## now start trying to read 256 bytes
tpdd_read(256)
tpdd_wait()
tpdd_check()
tpdd_wait: :0
tpdd_read: l=256 tpdd_wait()
tpdd_check()
tpdd_wait: :0
tpdd_wait()
[...]
tpdd_check()
tpdd_wait: :0
tpdd_wait()
tpdd_check()
tpdd_wait: :0
## 256 bytes is just small enough to fit in a single TPDD packet, and so
there is no loop of chunks here.
## just one solid transaction containing the whole 256 bytes
## got 256 bytes
## this is an ascii hex pair representation, but what came over the wire
is the actual binary
## not 04 but the byte 0x04 etc.
04 EF 05 F3 03 9D 02 89 02 6D EF 04 6E 97 03 75 6E 69 85 02 6E 65 73 97
05 74 F9 02 6F 65 62 E1 03 EB 03 6E E7 05 9C 03 72 90 06 89 04 8F 04 22
8F 04 74 69 7A 61 85 05 8E 07 94 08 F3 07 87 03 75 6E F4 06 F3 04 F2 02
70 65 72 E5 05 73 61 6E E4 03 68 65 74 61 6D 69 6E E5 04 69 62 8C 09 F3
05 74 68 16 74 92 03 6C E5 04 92 04 8B 04 69 66 69 63 61 85 07 94 07 92
08 F3 06 F9 07 87 05 74 75 64 E5 03 91 03 75 74 86 07 94 08 F3 07 87 06
85 05 65 E5 07 F3 02 75 63 EB 03 6C 65 F4 03 73 E5 04 94 05 84 09 F3 05
F3 04 87 01 EE 02 61 20 72 6F 6E 8D 0A 74 69 E3 04 6F 6E 64 E1 03 67 72
61 ED 02 90 04 67 65 73 69 E3 04 6F 67 18 F3 06 8F 06 F9 04 79 73 65 F3
06 69 F3 06 F4 07 F3 05 74 69 E3 08 61 EC 05 7A E5 06 94 06 87 03 72 20
8D 08 F4 06 F9 03 21 65 6D E1 04 6F 6D 69 63 90
## above is the raw data because of DEBUG=4
## this is the normal non-debug formatted output
## L 02 05 0256 : ... = L means logical sector, physical=02 logical=05
length=256 ; 256 hex pairs...
L 02 05 0256 : 04 EF 05 F3 03 9D 02 89 02 6D EF 04 6E 97 03 75 6E 69 85
02 6E 65 73 97 05 74 F9 02 6F 65 62 E1 03 EB 03 6E E7 05 9C 03 72 90 06
89 04 8F 04 22 8F 04 74 69 7A 61 85 05 8E 07 94 08 F3 07 87 03 75 6E F4
06 F3 04 F2 02 70 65 72 E5 05 73 61 6E E4 03 68 65 74 61 6D 69 6E E5 04
69 62 8C 09 F3 05 74 68 16 74 92 03 6C E5 04 92 04 8B 04 69 66 69 63 61
85 07 94 07 92 08 F3 06 F9 07 87 05 74 75 64 E5 03 91 03 75 74 86 07 94
08 F3 07 87 06 85 05 65 E5 07 F3 02 75 63 EB 03 6C 65 F4 03 73 E5 04 94
05 84 09 F3 05 F3 04 87 01 EE 02 61 20 72 6F 6E 8D 0A 74 69 E3 04 6F 6E
64 E1 03 67 72 61 ED 02 90 04 67 65 73 69 E3 04 6F 67 18 F3 06 8F 06 F9
04 79 73 65 F3 06 69 F3 06 F4 07 F3 05 74 69 E3 08 61 EC 05 7A E5 06 94
06 87 03 72 20 8D 08 F4 06 F9 03 21 65 6D E1 04 6F 6D 69 63 90
## back to command prompt to get a new command
PDD(fdc:6.2,F)>
----------------------------------------------------------------
phew...
--
bkw