From: duck@pembvax1.pembroke.edu
Newsgroups: comp.sys.cbm
Subject: C= Hacking Issue #4 3/5
Date: 5 Oct 92 14:01:45 GMT
Organization: Pembroke State University
NNTP-Posting-Host: pembvax1.pembroke.edu

24 $18  VM13  VM12  VM11  VM10  CB13  CB12  CB11  -     Memory Pointers
25 $19  IRQ   -     -     -     ILP   IMMC  IMBC  IRST  Interrupt Register
26 $1a  -     -     -     -     ELP   EMMC  EMBC  ERST  Enable Interrupt
27 $1b  M7DP  M6DP  M5DP  M4DP  M3DP  M2DP  M1DP  M0DP  MOB-DATA Priority
28 $1c  M7MC  M6MC  M5MC  M4MC  M3MC  M2MC  M1MC  M0MC  MOB Multicolor select
29 $1d  M7XE  M6XE  M5XE  M4XE  M3XE  M2XE  M1XE  M0XE  MOB X-Expand
30 $1e  M7M   M6M   M5M   M4M   M3M   M2M   M1M   M0M   MOB-MOB  Collision
31 $1f  M7D   M6D   M5D   M4D   M3D   M2D   M1D   M0D   MOB-DATA Collision

32 $20  -     -     -     -     EC3   EC2   EC1   EC0   Exterior Color
33 $21  -     -     -     -     B0C3  B0C2  B0C1  B0C0  Background #0 Color
34 $22  -     -     -     -     B1C3  B1C2  B1C1  B1C0  Background #1 Color
35 $23  -     -     -     -     B2C3  B2C2  B2C1  B2C0  Background #2 Color
36 $24  -     -     -     -     B3C3  B3C2  B3C1  B3C0  Background #3 Color
37 $25  -     -     -     -     MM03  MM02  MM01  MM00  MOB Multicolor #0
38 $26  -     -     -     -     MM13  MM12  MM11  MM10  MOB Multicolor #1

39 $27  -     -     -     -     M0C3  M0C2  M0C1  M0C0  MOB 0 Color
40 $28  -     -     -     -     M1C3  M1C2  M1C1  M1C0  MOB 1 Color
41 $29  -     -     -     -     M2C3  M2C2  M2C1  M2C0  MOB 2 Color
42 $2a  -     -     -     -     M3C3  M3C2  M3C1  M3C0  MOB 3 Color
43 $2b  -     -     -     -     M4C3  M4C2  M4C1  M4C0  MOB 4 Color
44 $2c  -     -     -     -     M5C3  M5C2  M5C1  M5C0  MOB 5 Color
45 $2d  -     -     -     -     M6C3  M6C2  M6C1  M6C0  MOB 6 Color
46 $2e  -     -     -     -     M7C3  M7C2  M7C1  M7C0  MOB 7 Color

MnX  = MOB n X position         MnY  = MOB n Y position
RC   = Raster compare register  ECM  = Extended color mode
MBB  = Bit map mode             DEN  = Display enable
RSEL = Row select               Y    = Screen Y position
LPX  = Light pen X position     LPY  = Light pen Y position
MnE  = MOB n Enable             RES  = Always set to zero!
MCM  = Multicolor mode          CSEL = Column select
X    = Screen X position        MnYE = MOB n Y expand
VM   = Video matrix pointer     CB   = Character base pointer
MnDP = MOB to data priority     MnMC = MOB n multicolor select
MnXE = MOB n X expand

=============================================================================
LITTLE RED READER: MS-DOS file reader for the 128 and 1571/81 drives.

by Craig Bruce  <csbruce@neumann.uwaterloo.ca>

1. INTRODUCTION

This article presents a program that reads MS-DOS files and the root directory
of MS-DOS disks.  The program copies only from drive to drive without
buffering file data internally.  This is simpler and imposes no limits on the
size of the files transferred, although it requires the use of two disk drives
(or a logical drive).  The user-interface code is written in BASIC and
presents a full-screen file selection menu.  The grunt-work code is written in
assembly language and operates at maximum velosity.

The Burst Command Instruction Set of the 1571/81 is used to read the MS-DOS
disk blocks and the standard kernel routines are used for outputting the
data.  (I am an operating systems specialist, so I call it a kernEl!)  Thus,
the MS-DOS files must be read from a 1571 or 1581 disk drive, but the output
device may be any disk drive type, the screen or a printer, or a virtual drive
type such as RAMLink, RAMDrive, or RAMDOS (for the REU).  It is interesting to
note that the data can be read in from an MS-DOS disk faster than it can be
written out to a 1571, 1581, or even a RAMDOS file.  A RAMLink can swallow the
data only slightly faster than it can be read.

Little Red Reader (LRR) supports double density 3.5" disks formatted with 80
tracks, 9 sectors per track, and 2 sides with a 1581 and 5.25" double density
disks formatted with 40 tracks, 9 sectors per track, and 2 sides with a 1571.
A limit of 128 directory entries and 3 File Allocation Table (FAT) sectors is
imposed.  There must be 2 copies of the FAT and the cluster size may be 1 or 2
sectors.  The sector size must be 512 bytes.

Oh, about the name.  It is a play on the name of another MS-DOS file copier
available for the C-128.  "Little" means that it is smaller in scope than the
other program, and "Red" is a different primary color to avoid any legal
complications.  It is also the non-white color of the flag of the country of
origin of this program (no, I am not Japanese).  Also, this program is Public
Domain Software, as is all software I develop for 8-bit Commodore Computers.
Feel free to E-mail me if you have questions or comments about this article.

2. USER GUIDE

LOAD and RUN the "lrr.128" BASIC program file.  When the program is first run,
it will display an "initializing" message and will load in the binary machine
language package from the "current" Commodore DOS drive (the current drive is
obtained from PEEK(186) - the last device accessed).  The binary package is
loaded only on the first run and is not reloaded on subsequent runs if the
package ID field is in place.

2.1. MAIN SCREEN

The main screen of the program is then displayed.  The main screen of the
program will look something like this:

   MS-DEV=9    MS-TYPE=1581    CBM-DEV=8

   NUM  S  TRN  TYP  FILENAME  EXT  LENGTH
   ---  -  ---  ---  --------  ---  ------
     1  *  ASC  SEQ  HACK4     TXT  120732
     2     BIN  PRG  RAMDOS    SFX   34923

   D=DIRECTORY  M=MS-DEV  F=CBM-DEV Q=QUIT
   T=TOGGLE-COLUMN, C=COPY-FILES, +/- PAGE

except that immediately after starting up, "<no files>" will be displayed
rather than filenames.  The "MS-DEV" and "MS-TYPE" fields give the device
number and type of the drive containing the MS-DOS disk to copy from, and the
"CBM-DEV" gives the device number of the drive/virtual drive/character device
to copy file data to.

Information about all MS-DOS files in the root directory of the MS-DOS disk is
displayed in columns below the drive information.  "NUM" gives the number of
the MS-DOS file in the directory listing, and "S" indicates whether the file
is "selected" or not.  If the file is selected, an asterisk (*) is displayed;
otherwise, a blank is displayed.  When you later enter Copy Mode, only the
files that have been "selected" are copied.

The "TRN" field indicates the character translation scheme to be used when the
file is copied.  A value of "BIN" (binary) means no translation and a value of
"ASC" (ascii) means the file characters are to be translated from MS-DOS ASCII
(or "ASCII-CrLf") to PETSCII.  The "TYP" field indicates the type of
Commodore-DOS file to create for writing the MS-DOS file contents into.  The
possible values are "SEQ" (sequential) and "PRG" (program).  The values of the
TRN and TYP fileds are set independently, so you can copy binary data to SEQ
files and ascii data to PRG files if you wish.

The "FILENAME" and "EXT" fields give the filename and extension type of the
MS-DOS files and "LENGTH" gives the exact length of the files in bytes.  Note
that if you perform "ASC" translation on a file, its PETSCII version will have
a shorter length.

2.2. USER COMMANDS

The bottom of the screen gives the command summary.  After starting the
program, you will want to setup the MS-DOS and CBM-DOS drives with the "M" and
"F" commands.  Simply press the (letter) key corresponding to the command
name to activate the command.  Pressing M will prompt you for the MS-DOS Drive
Number and the MS-DOS Drive Type.  In both cases, type the number and press
RETURN.  (Sorry for insulting all non-novices out there, but I want to be
complete).  The MS-DOS drive number cannot be the same as the CBM-DOS drive
number (since the program copies from drive-to-drive without internal
buffering).  For the drive type, enter an "8", "81", or "1581" for a 1581
drive or anything else for a 1571 drive.

Pressing F will prompt you for the CBM-DOS device number.  You may enter a
number from 0 to 30, except that it must not be the MS-DOS drive number.
Enter a "1" for Cassette Drive (God forbid!), a "3" for the screen, a "4" for
the printer (with an automatic secondary address of 7 (lowercase)), any number
above 7 for a Commodore disk drive or special virtual drive, or a value of "0"
for the special "null" drive.  A CBM-DEV value of 0 will case the program to
read MS-DOS files and do nothing with the output.  You can use this feature to
check out the raw reading speed of the program.

After setting up the drives, press D to read in the root directory off the
MS-DOS disk.  The data will come blazing in from the disk but BASIC will take
its good ole time sifting through it.  Filenames are displayed on the screen
as they are scanned in.  The program will (eventually) return to the main
screen and display the formatted file information.  One note: the process of
logging in a 1581 MS-DOS disk takes about 12 seconds (on my 1581, anyway), so
be patient.  An MS-DOS disk will have to be "logged in" every time you change
MS-DOS disks.  (Disks are logged in automatically).

A couple of notes about accessing MS-DOS disks: don't try to access a device
that is not present because the machine language routines cannot handle this
error for some reason and will lock up, requiring a STOP+RESTORE.  Also, make
sure that an actual MS-DOS disk is loaded into the drive.  If you accidentally
place Commodore-DOS disk into the MS-DOS drive, the 1581 will report an
invalid boot parameters error (#60), but a 1571 will lock up (since I don't
check the sector size and my burst routines are expecting 512 bytes to come
out of a sector whereas Commodore disks have only 256 bytes per sector).

Now you are ready to pick what files you want copied and how you want them
copied.  You will notice that a "cursor" appears in the "S" column of the
first file.  You may move the cursor around with the cursor keys: UP, DOWN,
LEFT, RIGHT, HOME, and CLR.  CLR (SHIFT-HOME) will move the cursor back to the
first file on the first screen.  You can move the cursor among the select,
translation, and file-type columns of all the files.  Pressing a SPACE or a
RETURN will toggle the value of the field that the cursor is on.  To toggle
all of the values of the "cursor" column (including files on all other
screens), press T.  You will notice that moving the cursor around and toggling
fields is a bit sluggish, especially if you are in Slow mode on the 40-column
screen.  Did I mention that this program will run on either the 40 or
80-column screen?  Toggling an entire column can take a couple of seconds.

If there are more than 18 MS-DOS files, you can press the "+" and "-" keys to
move among all of the screens of files.  The cursor movement keys will wrap
around on the current screen.  "+" is page forward, and "-" is page backward.
The screens wrap around too.

After you have selected all of the files you want to copy and their translation
and file-type fields have been set, press the C key to go into Copy Mode (next
section).  After copying, you are returned to the main screen with all of the
field settings still intact.  To exit from the program, press Q.

2.3. COPY MODE

When you enter copy mode, the screen will clear and the name of each selected
file is displayed as it is being copied.  If an error is encountered on either
the MS-DOS or CBM-DOS drive during copying, an error message will be displayed
and copying will continue (after you press a key for MS-DOS errors).

To generate a CBM-DOS filename from an MS-DOS filename, the eight filename
characters are taken (including spaces) and a dot (.) and the three characters
of the extension are appended.  Then, all spaces are removed, and if the name
ends with a dot (.) character, then that dot character is removed as well.  I
think this is fairly reasonable.

If there already is a file with the same filename on the CBM-DOS disk, then
you will be prompted if you want to overwrite the file or not.  Entering an
"n" will abort the copying of that file and go on to the next file, and
entering a "y" (or anything else) will cause the CBM-DOS file to be
"scratched" and then re-written.

The physical copying of the file is done completely in machine language and
nothing is displayed on the screen while this is happening, but you can follow
things by looking at das blinkin lichtes and listening for clicks and grinds.
You will probably be surprised by the MS-DOS file reading speed (I mean in a
good way).  The disk data is read in whole tracks and cached in memory and the
directory information and the FAT are retained in memory as well.  The result
is that minimal time is spent reading disk data, and no costly seeks are
required for opening a new MS-DOS file.  A result is that small files are
copied one after another very quickly.  You will have to wait, however, on the
relatively slow standard kernel/Commodore-DOS file writing.

A few changes had to be made to the program to accomodate the RAMDOS program.
RAMDOS uses memory from $2300 to $3FFF of RAM0, which is not really a good
place for a device driver, and it uses some of the zero-page locations that I
wanted to use.  But, difficulties were overcome.  The importance of RAMDOS
compatibility is that if you only have one disk drive but you have an REU, you
can use RAMDOS to store the MS-DOS files temporarily.  If you only have one
disk drive and no REU, you are SOL (Out of Luck) unless you can get a
RamDisk-type program for an unexpanded 128.  The RAMDOS program is available
from FTP site "ccosun.caltech.edu" in file "/pub/rknop/util128/ramdosii.sfx".
One note I found out about RAMDOS: you cannot use a

DOPEN#1,(CF$),U(CD),W

with it like you are supposed to be able to; you have to use a

DOPEN#1,(CF$+",W"),U(CD)

Here is a table of copying speeds for copying from 1571s and 1581s with ASC
and BIN translation modes.  All figures are in bytes/second.  These results
were obtained from copying a 127,280 byte text file (the text of C= Hacking
Issue #3).

   FROM  \ TO: "null"     RAMLink     RAMDOS     JD1581     JD1571
   -------+    ------     -------     ------     ------     ------
   81-bin |      5772        3441       2146        n/a        644
   81-asc |      5772        3434       2164        n/a        661
   71-bin |      4323        2991       1949       1821        n/a
   71-asc |      4323        2982       1962       1847        n/a

The "null" device is that "0" CBM-DOS device number, and a couple of entries
are "n/a" since I only have one 1571 and one 1581.  Note that my 71 and 81 are
JiffyDOS-ified, so the performance of a stock 71/81 will be poorer.  JiffyDOS
gives about a 2x performance improvement for the standard file accessing calls
(open, close, chrin, chrout).  RAMDOS doesn't seem to be as snappy as you
might think.

The "null" figures are quite impressive, but the raw sector reading speed
without the overhead of mucking around with file organization is 6700
bytes/sec for a 1581 and 4600 B/s for a 71.  The reason that the 1571 operates
so quickly is that I use a sector interleave of 4 (which is optimal) for
reading the tracks.  I think that other MS-DOS file copier program uses an
interleave of 1 (which is not optimal).  I lose some of the raw performance
because I copy the file data internally once before outputting it (to simplify
some of the code).

In a couple of places you will notice that ASC translation gives slightly
better or slightly worse performance than BIN.  This is because although
slightly more work is required to translate the characters, slightly fewer
characters will have to be written to the CBM-DOS file, since PETSCII uses
only CR where MS-DOS ASCII uses CR and LF to represent end-of-line.
Translation is done by using a table (that you can change if you wish).  Many
entries in this table contain a value of zero, which means that no character
will be output on translation.  Most of the control characters and all of the
characters of value 128 (0x80) or greater are thrown away on being
translated.  The table is set up so that CR characters are thrown away and the
LF character is translated to a CBM-DOS CR character.  Thus, both MS-DOS ASCII
files and UNIX ASCII files can be translated correctly.

2. BURST COMMANDS

Three burst commands from the 1571/81 disk drive Burst Command Instruction Set
are required to allow this program to read the MS-DOS disks: Query Disk
Format, Sector Interleave, and Read.  The grungy details about issuing burst
commands and burst mode handshaking are covered in C= Hacking Issue #3.  The
Query Disk Format command is used to "log in" the MS-DOS disk.  The Inquire
Disk burst command cannot be used with an MS-DOS disk on the 1581 for some
unknown reason.  I found this out the hard way.  The Query Disk Format command
has the following format:

    BYTE \ bit: 7     6     5     4     3     2     1     0  | Value
   -------+--------+-----+-----+-----+-----+-----+-----+-----+-------
     0    |     0  |  1  |  0  |  1  |  0  |  1  |  0  |  1  |  "U"
     1    |     0  |  0  |  1  |  1  |  0  |  0  |  0  |  0  |  "0"
     2    |     F  |  X  |  X  |  S  |  1  |  0  |  1  |  N  |  10
   -------+--------------------------------------------------+-------

where the F, S, and N bits have a value of 0 for our purposes.  A response of
a burst status byte and six other throw-away bytes is given from the drive.
This command takes quite a long time to execute on my 1581 but works quite
quickly on my 1571.  You only have to log in a disk whenever you change
disks.

The Sector Interleave command is used to set a soft interleave for the Read
command.  I use an interleave of 1 for the 1581 and an interleave of 4 for the
1571.  This means that the MS-DOS sectors will come from 1571 to the computer
in the following order: 1, 5, 9, 4, 8, 3, 7, 2, 6 (there are 9 sectors per
track on an MS-DOS disk (both 3.5" and 5.25"), numbered from 1 to 9).  LRR
handles the data coming in in this order, and in straight order from the
1581.  The Sector Interleave command has the following format, where the W and
N bits are 0 for us:

    BYTE \ bit: 7     6     5     4     3     2     1     0  | Value
   -------+--------+-----+-----+-----+-----+-----+-----+-----+-------
     0    |     0  |  1  |  0  |  1  |  0  |  1  |  0  |  1  |  "U"
     1    |     0  |  0  |  1  |  1  |  0  |  0  |  0  |  0  |  "0"
     2    |     W  |  X  |  X  |  0  |  1  |  0  |  0  |  N  |   8
     3    |                   <interleave>                   | 1 or 4
   -------+--------------------------------------------------+-------

The Read command is used to transfer the nine sectors of a track to the
computer in the order specified by the interleave.  The format is:

    BYTE \ bit: 7     6     5     4     3     2     1     0  | Value
   -------+--------+-----+-----+-----+-----+-----+-----+-----+-------
     0    |     0  |  1  |  0  |  1  |  0  |  1  |  0  |  1  |  "U"
     1    |     0  |  0  |  1  |  1  |  0  |  0  |  0  |  0  |  "0"
     2    |    T/L |  E  | B/X |  S  |  0  |  0  |  0  |  N  | 0 or 16
     3    |                      <track>                     |  ???
     4    |                      <sector>                    |   1
     5    |                <number of sectors>               |   9
   -------+--------------------------------------------------+-------

There are a couple of differences between the 1571 and 1581 versions of this
command.  Most important, the S bit (Side of disk to use) has the opposite
meaning on the two drives.  There's no good reason that I know of for this
inconsistency.  This is the reason that LRR needs to know what type of MS-DOS
drive it is dealing with (plus interleaving).

The read command returns the following data using burst mode handshaking:

       +-------------------+
     0 | Burst Status Byte |
       +-------------------+
     1 |                   |
   ... +  512 Data Bytes   |
   512 |                   |
       +-------------------+

for each sector transferred.  If the Burst Status Byte indicates an error,
then the data is not transferred and none of the following sectors are
either.  If the status byte gives a "Disk Changed" error, then you have to log
in the disk with the Query Disk Format command before read will work
properly.  This is actually a good feature since it lets you know about a disk
change so you can update any data structures you may have.  LRR simply re-logs
in the disk without updating any data structures and re-tries the failed read
operation.

3. MS-DOS DISK FORMAT

An MS-DOS disk is separated into 4 different parts: the Boot Sector, the
FAT(s), the Root Directory, and the File Data Sectors.  The logical sectors
(blocks) of a disk are numbered from 0 to some maximum number (1439 for a
3.5", 719 for a 5.25" DD disk).  The physical layout and the logical sector
numbers typically used by a 3.5" disk are shown here:

         +-------------------+
       0 |    Boot Sector    |
         +-------------------+
    1..3 |    FAT copy #1    |
         +-------------------+
    4..6 |    FAT copy #2    |
         +-------------------+
   7..14 |  Root Directory   |
         +-------------------+
      15 |                   |
     ... | File Data Sectors |
    1439 |                   |
         +-------------------+

3.1. THE BOOT SECTOR

The Boot Sector is always at logical sector number 0.  It contains some
important information about the format of the disk and it also contains code
to boot an MS-DOS machine from.  We aren't concerned with the bootstrapping
code, but the important values we need to obtain from the boot sector are:

   ABBR     OFFSET     1571     1581     DESCRIPTION
   ----     ------     ----     ----     -----------
    CS        13          2        2     Cluster size in sectors
    NB        14          1        1     Number of boot sectors
    NF        16          2        2     Number of FATs
    FL        23          2        3     FAT size in sectors
    DE        17        112      112     Number of root directory entries
    TS       19,20      720     1440     Total Number of sectors
    NS        24          9        9     Number of sectors per track
    NH        26          2        2     Number of sides

The 1571 and 1581 columns give the typical values of these parameters for
5.25" and 3.5" disks.  The OFFSET is the address of the parameter within the
boot sector.  The total number of sectors is given in low-byte, high-byte
order (since the 80x86 family is little-endian like the 6502 family).  From
the above parameters, we can derive the following important parameters:

   ABBR     FORMULA       1571     1581     DESCRIPTION
   ----      -------      ----     ----     -----------
    F1      NB+NF*FL         5        7     First root directory sector
    FS     NB+NF*FL+DE      12       14     First file data sector number
    NC     (TS-FS)/CS      354      713     Total number of file clusters

LRR imposes a number of limits on these parameters and will error-out if you
try to use a disk that is outside of LRR's limits.

3.2. CHEWING THE FAT

MS-DOS disks use a data structure called a File Allocation Table (FAT) to
record which clusters belong to which file in what order and which blocks are
free.  A cluster is a set of contiguous sectors which are allocated to files
as a group.  LRR handles cluster sizes of 1 and 2 sectors, giving a logical
file block size of 512 or 1024 bytes.  Typically, a cluster size of 2 sectors
is used.

The FAT is an array of 12-bit numbers, with an entry corresponding to each
cluster that can be allocated to files.  FAT entries 0 and 1 are reserved.  If
a FAT entry contains a value of $000, then the corresponding cluster is free
and can be allocated to a file; otherwise, the cluster is allocated and the
FAT entry contains the number of the NEXT FAT entry that belongs to the file.
Thus, MS-DOS files are stored in a singly-linked list of clusters like
Commodore-DOS files are, except that the links are not in the data sectors but
rather are in the FAT.  The pointer to the first FAT entry for a file is given
in the file's directory entry.

A special NULL/NIL pointer value of $FFF is used to indicate the end of the
chain of clusters allocated to a file.  This value is stored in the FAT entry
of the last cluster allocated to a file (of course).  Consider the following
example FAT:

   ENTRY       VALUE
   -----       -----
    $000        $FFF
    $001        $FFF
    $002   |----$003 <------Directory Entry
    $003   +--> $005----+
    $004        $000    |
    $005        $FFF <--+

Entries 0 and 1 are insignificant since they are reserved.  Say that a file
starts at FAT entry #2.  Then, it consists of the following chain of clusters:
2, 3, and 5.  Cluster #4 is free.  Clusters can be allocated to a file in
random order, but if they are allocated contiguously in forward order, then
the file will be able to be read faster.  The FAT is such an important data
structure that typically two copies are kept on the disk incase one of them
should become corrupted.

The MS-DOS designers were a little sneaky in storing the 12-bit FAT entries -
they used only 12 real bits per entry.  Ie., they store two FAT entries in
three bytes, where the two entries share the two nybbles of the middle byte.
The following diagram shows how the nybbles 1 (high), 2 (mid), and 3 (low) are
stored into FAT entries A and B:

     BYTE:       0            1            2
             +---+---+    +---+---+    +---+---+
    ENTRY:   | A | A |    | B | A |    | B | B |
   NYBBLE:   | 2 | 3 |    | 3 | 1 |    | 1 | 2 |
             +---+---+    +---+---+    +---+---+

Anyway, let's just say it's a bit tricky to extract the 12-bit values from
this compressed data structure.  On top of that, I don't think there is any
saving in disk space resulting from compressing this structure; they might as
well have just used a 16-bit FAT (like they do nowadays on larger disks).

3.3. THE ROOT DIRECTORY

The root directory has a fixed size, although I don't think that
subdirectories do.  LRR cannot access subdirectories.  Each 512-byte sector of
the root directory contains sixteen 32-byte directory entries.  One directory
entry is required for each file stored on the disk.  A directory entry has the
following structure:

   OFFSET     LEN     DESCRIPTION
   ------     ---     -----------
     0..7       8     Filename
    8..10       3     Extension
       11       1     <unused?>
       12       1     Attributes: $10=Directory, $08=VolumeId
   13..21       9     <unused>
   22..25       4     Date
   26..27       2     Starting FAT entry number
   28..31       4     File length in bytes

The filename and extension are stored with trailing padding spaces.  If a
directory entry is unused or deleted, then the first character of the filename
is either a $00 or a $E5 (229).  This is why you have to provide the first
character of a filename if you are undeleting a file on an MS-DOS machine.
Note that there is enough unused space that Microsoft or IBM could have
ditched the annoying 8+3 character filename format.

The attributes bits tell whether the directory entry is for a regular file, a
subdirectory, a disk volume name (in which case there is no file data), and a
couple of other things I can't remember.  I'm not sure about the exact
position or format of the date, but LRR doesn't use it anyway.  The starting
FAT entry number and the file length are stored in lower-byte-first order.

3.4. THE FILE DATA SPACE

The ramainder of the disk space is used for storing file data in clusters of 1
or 2 sectors each.  Given a cluster number (which is also the FAT entry
number), the following formula is used to calculate the starting logical
sector number of the cluster:

(ClusterNumber - 2) * ClusterSizeInBlocks + FirstFileDataLogicalSectorNumber

where "FirstFileDataLogicalSectorNumber" is the "FS" parameter derived
earlier.  The following consecutive logical sector numbers up to the number of
sectors per cluster form the rest of the cluster.  Note that a single cluster
can span sectors from one side of the disk to another or from one track to
another.  We perform the "(ClusterNumber - 2)" portion of the calculation
since the first two FAT entries are reserved.

Since the Read burst command of the 1571/81 wants the side, track, and sector
number of a sector rather than its logical number, we also need formulae for
these conversions:

   Track  = LogicalSectorNumber / 18
   Sector = LogicalSectorNumber % 9 + 1
   Side   = (LogicalSectorNumber / 9) % 2

These formulae are more problematic than the previous one since they require
division by 9 and 18.  LRR uses the method of repeated subtraction to perfrom
the necessary division (only one division is necessary).  The above formulae
imply that sequential logical sectors are stored on the top of the disk
first and then the bottom of the disk of the same track, and then on the top
of the next track, etc.  This is a good sector numbering scheme (unlike the
CBM-DOS scheme for 1571 sectors) since it is faster to switch sides of the
disk than it is to switch tracks, so you can read the disk faster.

Oh yeah, the way that you know how many file data bytes are in the last
cluster of a file chain (the cluster with the NULL FAT entry) is to take the
file length from the directory entry and "mod" (the C language % operator) it
with the cluster size.  One special case is if this calculation results in a
zero, then the last cluster is completely full (rather than completely empty
as the calculation would suggest).  This calculation is easily done in
machine language with an AND operation since the cluster size is always a
power of two.

4. FILE COPYING PACKAGE

This section discusses the interface to and implementation of the MS-DOS file
copying package.  It is written in assembly language and is loaded into memory
at address $8000 on bank 0 and requires about 13K of memory.  The package is
loaded at this high address to be out of the way of the main BASIC program,
even if RAMDOS is installed.

4.1. INTERFACE

The subroutine call and parameter passing interface to the file copying
package is summarized as follows:

   ADDRESS     DESCRIPTION
   -------     -----------
   PK          InitPackage subroutine
   PK+3        LoadDirectory subroutine
   PK+6        CopyFile subroutine
   PK+9        two-byte package identification number
   PK+15       errno : error code returned
   PK+16       MS-DOS device number (8 to 30)
   PK+17       MS-DOS device type ($00=1571, $FF=1581)
   PK+18       two-byte starting cluster number for file copying
   PK+20       low and mid bytes of file length for copying

where "PK" is the load address of the package.  Additional subroutine
parameters are passed in the processor registers.

The "InitPackage" subroutine should be called when the package is first
installed, whenever the MS-DOS device number is changed, and whenever a new
disk is mounted to invalidate the internal track cache.  It requires no
parameters.

The "LoadDirectory" subroutine will load the directory, FAT, and the Boot
Sector parameters into the internal memory of the package from the current
MS-DOS device number.  No (other) input parameters are needed and the
subroutine returns a pointer to the directory space in the .AY registers and
the number of directory entries in the .X register.  If an error occurs, then
the subroutine returns with the Carry flag set and the error code is available
in the "errno" interface variable.  The directory entry data is in the
directory space as it was read in raw from the directory sectors on the MS-DOS
disk.

The "CopyFile" subroutine will copy a single file from the MS-DOS disk to a
specified CBM-Kernal logical file number (the CBM file must already be
opened).  If the CBM logical file number is zero, then the file data is simply
discarded after it is read from the MS-DOS file.  The starting cluster number
of the file to copy and the low and mid bytes of the file length are passed in
the PK+18 and PK+20 interface words.  The translation mode to use is passed in
the .A register ($00=binary, $FF=ascii) and the CBM logical file number to
output to is passed in the .X register.  If an error occurs, the routine
returns with the Carry flag set and the error code in the "errno" interface
variable.  There are no other output parameters.

Note that since the starting cluster number and low-file length of the file to
be copied are required rather than the filename, it is the responsibility of
the front-end application program to dig through the raw directory sector data
to get this information.  The application must also open the Commodore-DOS
file of whatever filetype on whatever device is required; the package does not
need to know the Commodore-DOS device number.

The MS-DOS device number and device type interface variables allow you to set
the MS-DOS drive and the package identification number allows the application
program to check if the package is already loaded into memory so that it only
has to load the package the first time the application is run and not on
re-runs.  The identification sequence is a value of $CB followed by a value
of 131.

4.2. IMPLEMENTATION

This section presents the code that implements the MS-DOS file reading
package.  It is here in a special form; each code line is preceded by a few
special characters and the line number.  The special characters are there to
allow you to easily extract the assembler code from the rest of this magazine
(and all of my ugly comments).  On a Unix system, all you have to do is
execute the following command line (substitute filenames as appropriate):

grep '^\.%...\!' Hack4 | sed 's/^.%...\!..//' | sed 's/.%...\!//' >lrr.s

You'll notice that the initial comment lines here were an afterthought.

.%000!  ;Little Red Reader MS-DOS file copier program
.%000!  ;written 92/10/03 by Craig Bruce for C= Hacking Net Magazine
.%000!

The code is written for the Buddy assembler and here are a couple setup
directives.  Note that my comments come before the section of code.

.%001!  .org $8000
.%002!  .obj "@:lrr.bin"
.%003!
.%004!  ;====jump table and parameters interface ====
.%005!
.%006!  jmp initPackage
.%007!  jmp loadDirectory
.%008!  jmp copyFile
.%009!
.%010!  .byte $cb,131   ;identification
.%011!  .byte 0,0,0,0
.%012!

These variables are included in the package program space to minimize unwanted
interaction with other programs loaded at the same time, such as the RAMDOS
device driver.

.%013!  errno           .buf 1    ;(location pk+15)
.%014!  sourceDevice    .buf 1
.%015!  sourceType      .buf 1    ;$00=1571, $ff=1581
.%016!  startCluster    .buf 2
.%017!  lenML           .buf 2    ;length medium and low bytes
.%018!
.%019!  ;====global declaraions====
.%020!
.%021!  kernelListen = $ffb1
.%022!  kernelSecond = $ff93
.%023!  kernelUnlsn  = $ffae
.%024!  kernelAcptr  = $ffa2
.%025!  kernelCiout  = $ffa8
.%026!  kernelSpinp  = $ff47
.%027!  kernelChkout = $ffc9
.%028!  kernelClrchn = $ffcc
.%029!  kernelChrout = $ffd2
.%030!
.%031!  st = $d0
.%032!  ciaClock = $dd00
.%033!  ciaFlags = $dc0d
.%034!  ciaData  = $dc0c
.%035!

These are the parameters and derived parameters from the boot sector.  They
are kept in the program space to avoid interactions.

.%036!  clusterBlockCount .buf 1        ;1 or 2
.%037!  fatBlocks         .buf 1        ;up to 3
.%038!  rootDirBlocks     .buf 1        ;up to 8
.%039!  rootDirEntries    .buf 1        ;up to 128
.%040!  totalSectors      .buf 2        ;up to 1440
.%041!  firstFileBlock    .buf 1
.%042!  firstRootDirBlock .buf 1
.%043!  fileClusterCount  .buf 2
.%044!

The cylinder (track) and side that is currently stored in the trach cache.

.%045!  bufCylinder     .buf 1
.%046!  bufSide         .buf 1
.%047!  formatParms     .buf 6
.%048!

This package is split into a number of levels.  This level interfaces with the
Kernal serial bus routines and the burst command protocol of the disk drives.

.%049!  ;====hardware level====
.%050!

Connect to the MS-DOS device and send the "U0" burst command prefix and the
burst command byte.

.%051!  sendU0 = *  ;( .A=burstCommandCode ) : .CS=err
.%052!     pha
.%053!     lda #0
.%054!     sta st
.%055!     lda sourceDevice
.%056!     jsr kernelListen
.%057!     lda #$6f
.%058!     jsr kernelSecond
.%059!     lda #"u"
.%060!     jsr kernelCiout
.%061!     bit st
.%062!     bmi sendU0Error
.%063!     lda #"0"
.%064!     jsr kernelCiout
.%065!     pla
.%066!     jsr kernelCiout
.%067!     bit st
.%068!     bmi sendU0Error
.%069!     clc
.%070!     rts
.%071!
.%072!     sendU0Error = *
.%073!     lda #5
.%074!     sta errno
.%075!     sec
.%076!     rts
.%077!

Toggle the "Data Accepted / Ready For More" clock signal for the burst
transfer protocol.

.%078!  toggleClock = *
.%079!     lda ciaClock
.%080!     eor #$10
.%081!     sta ciaClock
.%082!     rts
.%083!

Wait for a burst byte to arrive in the serial data register of CIA#1 from the
fast serial bus.

.%084!  serialWait = *
.%085!     lda #$08
.%086!  -  bit ciaFlags
.%087!     beq -
.%088!     rts
.%089!

Wait for and get a burst byte from the fast serial bus, and send the "Data
Accepted" signal.

.%090!  getBurstByte = *
.%091!     jsr serialWait
.%092!     ldx ciaData
.%093!     jsr toggleClock
.%094!     txa
.%095!     rts
.%096!

Send the burst commands to "log in" the MS-DOS disk and set the Read sector
interleave factor.

.%097!  mountDisk = *  ;() : .CS=err
.%098!     lda #%00011010
.%099!     jsr sendU0
.%100!     bcc +
.%101!     rts
.%102!  +  jsr kernelUnlsn
.%103!     bit st
.%104!     bmi sendU0Error
.%105!     clc
.%106!     jsr kernelSpinp
.%107!     bit ciaFlags
.%108!     jsr toggleClock
.%109!     jsr getBurstByte
.%110!     sta errno
.%111!     and #$0f
.%112!     cmp #2
.%113!     bcs mountExit

Grab the throw-away parameters from the mount operation.

.%114!     ldy #0
.%115!  -  jsr getBurstByte
.%116!     sta formatParms,y
.%117!     iny
.%118!     cpy #6
.%119!     bcc -
.%120!     clc

Set the sector interleave to 1 for a 1581 or 4 for a 1571.

.%121!     ;** set interleave
.%122!     lda #%00001000
.%123!     jsr sendU0
.%124!     bcc +
-- 

- Craig Taylor   || Standard Disclaimer Applies (of course!)
duck@pembvax1.pembroke.edu (Pembroke State University)

Oregon, n.:
	Eighty billion gallons of water with no place to go on Saturday
	night.
Newsgroups: comp.sys.cbm