Copyright (c) 2012-2016 Hyperion Entertainment and contributors.

Trackdisk Device

From AmigaOS Documentation Wiki
Revision as of 20:28, 6 November 2015 by Steven Solie (Talk | contribs) (Device Interface)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Trackdisk Device

The Amiga trackdisk device directly drives the disk, controls the disk motors, reads raw data from the tracks, and writes raw data to the tracks. Normally, you use the AmigaDOS functions to write or read data from the disk. The trackdisk device is the lowest-level software access to the disk data and is used by AmigaDOS to access the disks. The trackdisk device supports the usual commands such as CMD_WRITE and CMD_READ. In addition, it supports an extended form of these commands to allow additional control over the trackdisk device.

Trackdisk Device Commands and Functions

Command Command Operation
CMD_CLEAR Mark track buffer as invalid. Forces the track to be re-read.
ETD_CLEAR ETD_CLEAR also checks for a diskchange.
CMD_READ Read one or more sectors from a disk.
ETD_READ ETD_READ also reads the sector label area and checks for a diskchange.
CMD_UPDATE Write out track buffer if it has been changed.
ETD_UPDATE ETD_UPDATE also checks for a diskchange.
CMD_WRITE Write one or more sectors to a disk.
ETD_WRITE ETD_WRITE also writes the sector label area and checks for a diskchange.
TD_ADDCHANGEINT Add an interrupt handler to be activated on a diskchange.
TD_CHANGENUM Return the current value of the diskchange counter used by the ETD commands to determine if a diskchange has occurred.
TD_CHANGESTATE Return the disk present/not-present status of a drive.
TD_EJECT Eject a disk from a drive. This command will only work on drives that support an eject command.
TD_FORMAT Initialize one or more tracks with a data buffer.
ETD_FORMAT ETD_FORMAT also initializes the sector label area.
TD_GETDRIVETYPE Return the type of disk drive in use by the unit.
TD_GETGEOMETRY Return the disk geometry table.
TD_GETNUMTRACKS Return the number of tracks usable with the unit.
TD_MOTOR Turn the motor on or off.
ETD_MOTOR ETD_MOTOR also checks for a diskchange.
TD_PROTSTATUS Return the write-protect status of a disk.
TD_RAWREAD Read RAW sector data from disk (unencoded MFM).
ETD_RAWREAD ETD_RAWREAD also checks for a diskchange.
TD_RAWWRITE Write RAW sector data to disk.
ETD_RAWWRITE ETD_RAWWRITE also checks for a diskchange.
TD_REMCHANGEINT Remove a diskchange interrupt handler.
TD_SEEK Move the head to a specific track.
ETD_SEEK ETD_SEEK also checks for a diskchange.

Device Interface

The trackdisk device operates like other Amiga devices. To use it, you must first open the device, then send I/O requests to it, and then close it when finished. See Exec Device I/O for general information on device usage.

The trackdisk device uses two different types of I/O request blocks, IOStdReq and IOExtTD and two types of commands, standard and extended. An IOExtTD is required for the extended trackdisk commands (those beginning with “ETD_”), but can be used for both types of commands. Thus, the IOExtTD is the type of I/O request that will be used in this article.

struct IOExtTD
{
    struct  IOStdReq iotd_Req;
    ULONG   iotd_Count;         /* Diskchange counter */
    ULONG   iotd_SecLabel;      /* Sector label data */
};

See the include file devices/trackdisk.h for the complete structure definition.

The enhanced commands listed above—those beginning with “ETD_”— are similar to their standard counterparts but have additional features: they allow you to control whether a command will be executed if the disk has been changed and they allow you to read or write to the sector label portion of a sector.

Enhanced commands require a larger I/O request, IOExtTD, than the IOStdReq request used by the standard commands. IOExtTD contains extra information needed by the enhanced command; since the standard form of a command ignores the extra fields, IOExtTD requests can be used for both types. The extra information takes the form of two extra longwords at the end of the data structure. These commands are performed only if the change count is less than or equal to the value in the iotd_Count field of the command’s request block.

The iotd_Count field keeps old I/O requests from being performed when the disk is changed. Any request found with an iotd_Count less than the current change counter value will be returned with a characteristic error (TDERR_DiskChange) in the io_Error field. This allows stale I/O requests to be returned to the user after a disk has been changed. The current disk-change counter value can be obtained by TD_CHANGENUM. If the user wants enhanced disk I/O but does not care about disk removal, then iotd_Count may be set to the maximum unsigned long integer value (0xFFFFFFFF).

The iotd_SecLabel field allows access to the sector identification section of the sector header. Each sector has 16 bytes of descriptive data space available to it; the trackdisk device does not interpret this data. If iotd_SecLabel is NULL, then this descriptive data is ignored. If it is not NULL, then iotd_SecLabel should point to a series of contiguous 16-byte chunks (one for each sector that is to be read or written). These chunks will be written out to the sector’s label region on a write or filled with the sector’s label area on a read. If a CMD_WRITE (the standard write call) is done, then the sector label area is left unchanged.

About Amiga Floppy Disks

The standard 3.5 inch Amiga floppy disk consists of a number of tracks that are NUMSECS (11) sectors of TD_SECTOR (512) usable data bytes plus TD_LABELSIZE (16) bytes of label area. There are usually 2 tracks per cylinder (2 heads) and 80 cylinders per disk. The number of tracks can be found using the TD_GETNUMTRACKS command.

The NUMSECS in some drives may be variable and may change when a disk is inserted. Use TD_GETGEOMETRY to determine the current number of sectors.

Think Tracks not Cylinders
The result is given in tracks and not cylinders. On a standard 3.5" drive, this gives useful space of 880K bytes plus 28K bytes of sector label area per floppy disk.

Although the disk is logically divided up into sectors, all I/O to the disk is done a track at a time. This allows access to the drive with no interleaving and increases the useful storage capacity by about 20 percent. Each disk drive on the system has its own buffer which holds the track data going to and from the drive.

Normally, a read of a sector will only have to copy the data from the track buffer. If the track buffer contains another track’s data, then the buffer will first be written back to the disk (if it is “dirty”) and the new track will be read in. All track boundaries are transparent to the programmer (except for FORMAT, SEEK, and RAWREAD/RAWWRITE commands) because you give the device an offset into the disk in the number of bytes from the start of the disk. The device ensures that the correct track is brought into memory.

The performance of the disk is greatly enhanced if you make effective use of the track buffer. The performance of sequential reads will be up to an order of magnitude greater than reads scattered across the disk. In addition, only full-sector writes on sector boundaries are supported.

The trackdisk device is based upon a standard device structure. It has the following restrictions:

  • All reads and writes must use an io_Length that is an integer multiple of TD_SECTOR bytes (the sector size in bytes).
  • The offset field must be an integer multiple of TD_SECTOR.
  • The data buffer must be word-aligned.

Opening the Trackdisk Device

Three primary steps are required to open the trackdisk device:

  • Create a message port by calling CreatePort(). Reply messages from the device must be directed to a message port.
  • Create an extended I/O request structure of type IOExtTD. The IOExtTD structure is created by the CreateExtIO() function.
  • Open the trackdisk device. Call OpenDevice(), passing it the extended I/O request.

For the trackdisk device, the flags parameter of the OpenDevice() function specifies whether you are opening a 3.5’’ drive (flags=0) or a 5.25’’ drive (flags=1). With flags set to 0 trackdisk will only open a 3.5’’ drive. To tell the device to open any drive it understands, set the flags parameter to TDF_ALLOW_NON_3_5. (See the include file devices/trackdisk.h for more information.)

#include <devices/trackdisk.h>
 
struct MsgPort *TrackMP;         /* Pointer for message port */
struct IOExtTD *TrackIO;         /* Pointer for IORequest */
 
if (TrackMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END))
{
    if (TrackIO = (struct IOExtTD *)
           IExec->AllocSysObjectTags(ASOT_IOREQUEST,
               ASOIOR_ReplyPort, TrackMP,
               ASOIOR_Size, sizeof(struct IOExtTD),
               TAG_END))
    {
        if (IExec->OpenDevice(TD_NAME,0L,(struct IORequest *)TrackIO,Flags))
        {
            IDOS->Printf("%s did not open\n", TD_NAME);
Disk Drive Unit Numbers
The unit number—second parameter of the OpenDevice() call—can be any value from 0 to 3. Unit 0 is the built-in 3.5’’ disk drive. Units 1 through 3 represent additional disk drives that may be connected to an Amiga system.

Reading from the Trackdisk Device

You read from the trackdisk device by passing an IOExtTD to the device with CMD_READ set in io_Command, the number of bytes to be read set in io_Length, the address of the read buffer set in io_Data and the track you want to read—specified as a byte offset from the start of the disk—set in io_Offset.

The byte offset of a particular track is calculated by multiplying the number of the track you want to read by the number of bytes in a track. The number of bytes in a track is obtained by multiplying the number of sectors (NUMSECS) by the number of bytes per sector (TD_SECTOR). Thus you would multiply 11 by 512 to get 5632 bytes per track. To read track 15, you would multiply 15 by 5632 giving 84,480 bytes offset from the beginning of the disk.

#define TRACK_SIZE ((int32) (NUMSECS * TD_SECTOR))
uint8 *Readbuffer;
 
if (Readbuffer = IExec->AllocVecTags(TRACK_SIZE, AVT_ClearWithValue, 0, TAG_END))
{
    DiskIO->iotd_Req.io_Length  = TRACK_SIZE;
    DiskIO->iotd_Req.io_Data    = (APTR)Readbuffer;
    DiskIO->iotd_Req.io_Offset  = (uint32)(TRACK_SIZE * track);
    DiskIO->iotd_Req.io_Command = CMD_READ;
    IExec->DoIO((struct IORequest *)DiskIO);
}

For reads using the enhanced read command ETD_READ, the IOExtTD is set the same as above with the addition of setting iotd_Count to the current diskchange number. The diskchange number is returned by the TD_CHANGENUM command (see below). If you wish to also read the sector label area, you must set iotd_SecLabel to a non-NULL value.

DiskIO->iotd_Req.io_Length  = TRACK_SIZE;
DiskIO->iotd_Req.io_Data    = (APTR)Readbuffer;
DiskIO->iotd_Req.io_Offset  = (uint32)(TRACK_SIZE * track);
DiskIO->iotd_Count          = change_count;
DiskIO->iotd_Req.io_Command = ETD_READ;
IExec->DoIO((struct IORequest *)DiskIO);

ETD_READ and CMD_READ obey all of the trackdisk device restrictions noted above. They transfer data from the track buffer to the user’s buffer. If the desired sector is already in the track buffer, no disk activity is initiated. If the desired sector is not in the buffer, the track containing that sector is automatically read in. If the data in the current track buffer has been modified, it is written out to the disk before a new track is read.

Writing to the Trackdisk Device

You write to the trackdisk device by passing an IOExtTD to the device with CMD_WRITE set in io_Command, the number of bytes to be written set in io_Length, the address of the write buffer set in io_Data and the track you want to write—specified as a byte offset from the start of the disk—set in io_Offset.

#define TRACK_SIZE ((int32) (NUMSECS * TD_SECTOR))
uint8 *Writebuffer;
 
if (Writebuffer = IExec->AllocVecTags(TRACK_SIZE, AVT_ClearWithValue, 0, TAG_END))
{
    DiskIO->iotd_Req.io_Length  = TRACK_SIZE;
    DiskIO->iotd_Req.io_Data    = (APTR)Writebuffer;
    DiskIO->iotd_Req.io_Offset  = (uint32)(TRACK_SIZE * tracknum);
    DiskIO->iotd_Req.io_Command = CMD_WRITE;
    IExec->DoIO((struct IORequest *)DiskIO);
}

For writes using the enhanced write command ETD_WRITE, the IOExtTD is set the same as above with the addition of setting iotd_Count to the current diskchange number. The diskchange number is returned by the TD_CHANGENUM command (see below). If you wish to also write the sector label area, you must set iotd_SecLabel to a non-NULL value.

DiskIO->iotd_Req.io_Length  = TRACK_SIZE;
DiskIO->iotd_Req.io_Data    = (APTR)Writebuffer;
DiskIO->iotd_Req.io_Offset  = (uint32)(TRACK_SIZE * tracknum);
DiskIO->iotd_Count          = change_count;
DiskIO->iotd_Req.io_Command = ETD_WRITE;
IExec->DoIO((struct IORequest *)DiskIO);

ETD_WRITE and CMD_WRITE obey all of the trackdisk device restrictions noted above. They transfer data from the user’s buffer to the track buffer. If the track that contains this sector is already in the track buffer, no disk activity is initiated. If the desired sector is not in the buffer, the track containing that sector is automatically read in. If the data in the current track buffer has been modified, it is written out to the disk before a new track is read in for modification.

Closing the Trackdisk Device

As with all devices, you must close the trackdisk device when you have finished using it. To release the device, a CloseDevice() call is executed with the same IOExtTD used when the device was opened. This only closes the device and makes it available to the rest of the system. It does not deallocate the IOExtTD structure.

IExec->CloseDevice((struct IORequest *)DiskIO);

Advanced Commands

Determining the Drive Geometry Table

The layout geometry of a disk drive can be determined by using the TD_GETGEOMETRY command. The layout can be defined three ways:

  • TotalSectors
  • Cylinders and CylSectors
  • Cylinders, Heads, and TrackSectors

Of the three, TotalSectors is the most accurate, Cylinders and CylSectors is less so, and Cylinders, Heads and TrackSectors is the least accurate. All are usable, though the last two may waste some portion of the available space on some drives.

The TD_GETGEOMETRY commands returns the disk layout geometry in a DriveGeometry structure:

struct DriveGeometry
{
    ULONG dg_SectorSize;        /* in bytes */
    ULONG dg_TotalSectors;      /* total # of sectors on drive */
    ULONG dg_Cylinders;         /* number of cylinders */
    ULONG dg_CylSectors;        /* number of sectors/cylinder */
    ULONG dg_Heads;             /* number of surfaces */
    ULONG dg_TrackSectors;      /* number of sectors/track */
    ULONG dg_BufMemType;        /* preferred buffer memory type */
                                /* (usually MEMF_PUBLIC) */
    UBYTE dg_DeviceType;        /* codes as defined in the SCSI-2 spec*/
    UBYTE dg_Flags;             /* flags, including removable */
    UWORD dg_Reserved;
};

See the include file devices/trackdisk.h for the complete structure definition and values for the dg_DeviceType and dg_Flags fields.

You determine the drive layout geometry by passing an IOExtTD with TD_GETGEOMETRY set in io_Command and a pointer to a DriveGeometry structure set in io_Data.

struct DriveGeometry *Euclid =
    IExec->AllocVecTags(sizeof(struct DriveGeometry),
        AVT_Type, MEMF_SHARED,
        AVT_ClearWithValue, 0,
        TAG_END);
 
DiskIO->iotd_Req.io_Data = Euclid;     /* put layout geometry here */
DiskIO->iotd_Req.io_Command = TD_GETGEOMETRY;
IExec->DoIO((struct IORequest *)DiskIO);

TD_GETGEOMETRY is preferred over TD_GETNUMTRACKS for determining the number of tracks on a disk. This is because new drive types may have more sectors or different sector sizes, etc., than standard Amiga drives.

Clearing the Track Buffer

ETD_CLEAR and CMD_CLEAR mark the track buffer as invalid, forcing a reread of the disk on the next operation. ETD_UPDATE or CMD_UPDATE would be used to force data out to the disk before turning the motor off. ETD_CLEAR or CMD_CLEAR is usually used after having locked out the trackdisk device via the use of the disk resource, when you wish to prevent the track from being updated, or when you wish to force the track to be re-read. ETD_CLEAR or CMD_CLEAR will not do an update, nor will an update command do a clear.

You clear the track buffer by passing an IOExtTD to the device with CMD_CLEAR or ETD_CLEAR set in io_Command. For ETD_CLEAR, you must also set iotd_Count to the current diskchange number.

DiskIO->iotd_Req.io_Command = TD_CLEAR;
IExec->DoIO((struct IORequest *)DiskIO);

Controlling the Drive Motor

ETD_MOTOR and TD_MOTOR give you control of the motor. When the trackdisk device executes this command, the old state of the motor is returned in io_Actual. If io_Actual is zero, then the motor was off. Any other value implies that the motor was on. If the motor is just being turned on, the device will delay the proper amount of time to allow the drive to come up to speed. Normally, turning the drive on is not necessary—the device does this automatically if it receives a request when the motor is off.

However, turning the motor off is the programmer’s responsibility. In addition, the standard instructions to the user are that it is safe to remove a disk if, and only if, the motor is off (that is, if the disk light is off).

You control the drive motor by passing an IOExtTD to the device with CMD_MOTOR or ETD_MOTOR set in io_Command and the state you want to put the motor in set in io_Length. If io_Length is set to 1, the trackdisk device will turn on the motor; a 0 will turn it off. For ETD_MOTOR, you must also set iotd_Count to the current diskchange number.

DiskIO->iotd_Req.io_Length = 1;          /* Turn on the drive motor */
DiskIO->iotd_Req.io_Command = TD_MOTOR;
IExec->DoIO((struct IORequest *)DiskIO);

Updating a Track Sector

The Amiga trackdisk device does not write data sectors unless it is necessary (you request that a different track be used) or until the user requests that an update be performed. This improves system speed by caching disk operations. The update commands ensure that any buffered data is flushed out to the disk. If the track buffer has not been changed since the track was read in, the update commands do nothing.

You update a data sector by passing an IOExtTD to the device with CMD_UPDATE or ETD_UPDATE set in io_Command. For ETD_UPDATE, you must also set iotd_Count to the current diskchange number.

DiskIO->iotd_Req.io_Command = TD_UPDATE;
IExec->DoIO((struct IORequest *)DiskIO);

Formatting a Track

ETD_FORMAT and TD_FORMAT are used to write data to a track that either has not yet been formatted or has had a hard error on a standard write command. TD_FORMAT completely ignores all data currently on a track and does not check for disk change before performing the command. The device will format the requested tracks, filling each sector with the contents of the buffer pointed to by io_Data field. You should do a read pass to verify the data.

If you have a hard write error during a normal write, you may find it possible to use the TD_FORMAT command to reformat the track as part of your error recovery process. ETD_FORMAT will write the sector label area if the iotd_SecLabel is non-NULL.

You format a track by passing an IOExtTD to the device with CMD_FORMAT or ETD_FORMAT set in io_Command, io_Data set to at least track worth of data, io_Offset field set to the byte offset of the track you want to write and the io_Length set to the length of a track. For ETD_FORMAT, you must also set iotd_Count to the current diskchange number.

#define TRACK_SIZE (NUMSECS * TD_SECTOR)
uint8 *Writebuffer;
 
if (WriteBuffer = AllocVecTags(TRACK_SIZE, AVT_Type, MEMF_SHARED, AVT_ClearWithValue, 0, TAG_END))
    {
    DiskIO->iotd_Req.io_Length  = TRACK_SIZE;
    DiskIO->iotd_Req.io_Data    =(APTR)Writebuffer;
    DiskIO->iotd_Req.io_Offset  =(uint32)(TRACK_SIZE * track);
    DiskIO->iotd_Req.io_Command = TD_FORMAT;
    IExec->DoIO((struct IORequest *)DiskIO);
    }

Ejecting a Disk

Certain disk drive manufacturers allow software control of disk ejection. The trackdisk device provides the TD_EJECT command to tell such drives to eject a disk.

You eject a disk by passing an IOExtTD to the device with TD_EJECT set in io_Command.

DiskIO->iotd_Req.io_Command = TD_EJECT;
IExec->DoIO((struct IORequest *)DiskIO);
Read the Instruction Manual
The 3.5’’ drives for the Amiga and most other Amiga drive manufacturers do not support software disk ejects. Attempting this command on those drives will result in an error condition. Consult the instruction manual for your disk drive to determine whether this is supported.

Disk Status Commands

Disk status commands return status on the current disk in the opened unit. These commands may be done with quick I/O and thus may be called within interrupt handlers (such as the trackdisk disk change handler). See Exec Device I/O for more detailed information on quick I/O.

Determining the Presence of a Disk

You determine the presence of a disk in a drive by passing an IOExtTD to the device with TD_CHANGESTATE set in io_Command. For quick I/O, you must set io_Flags to IOF_QUICK.

DiskIO->iotd_Req.io_Flags = IOF_QUICK;
DiskIO->iotd_Req.io_Command = TD_CHANGESTATE;
IExec->BeginIO((struct IORequest *)DiskIO);

TD_CHANGESTATE returns the presence indicator of a disk in io_Actual. The value returned will be zero if a disk is currently in the drive and nonzero if the drive has no disk.

Determining the Write-Protect Status of a Disk

You determine the write-protect status of a disk by passing an IOExtTD to the device with TD_PROTSTATUS set in io_Command. For quick I/O, you must set io_Flags to IOF_QUICK.

DiskIO->iotd_Req.io_Flags = IOF_QUICK;
DiskIO->iotd_Req.io_Command = TD_PROTSTATUS;
IExec->BeginIO((struct IORequest *)DiskIO);

TD_PROTSTATUS returns the write-protect status in io_Actual. The value will be zero if the disk is not write-protected and nonzero if the disk is write-protected.

Determining the Drive Type

You determine the drive type of a unit by passing an IOExtTD to the device with TD_GETDRIVETYPE set in io_Command. For quick I/O, you must set io_Flags to IOF_QUICK.

DiskIO->iotd_Req.io_Flags = IOF_QUICK;
DiskIO->iotd_Req.io_Command = TD_GETDRIVETYPE;
IExec->BeginIO((struct IORequest *)DiskIO);

TD_GETDRIVETYPE returns the drive type for the unit that was opened in io_Actual. The value will be DRIVE3_5 for 3.5’’ drives and DRIVE5_25 for 5.25’’ drives. The unit can be opened only if the device understands the drive type it is connected to.

Determining the Number of Tracks of a Drive

You determine the number of a tracks of a drive by passing an IOExtTD to the device with TD_GETNUMTRACKS set in io_Command. For quick I/O, you must set io_Flags to IOF_QUICK.

DiskIO->iotd_Req.io_Flags = IOF_QUICK;
DiskIO->iotd_Req.io_Command = TD_GETNUMTRACKS;
IExec->BeginIO((struct IORequest *)DiskIO);

TD_GETNUMTRACKS returns the number of tracks on that device in io_Actual. This is the number of tracks of TD_SECTOR * NUMSECS size. It is not the number of cylinders. With two heads, the number of cylinders is half of the number of tracks. The number of cylinders is equal to the number of tracks divided by the number of heads (surfaces). The standard 3.5’’ Amiga drive has two heads

TD_GETGEOMETRY is the preferred over TD_GETNUMTRACKS especially since new drive types may have more sectors or different sector sizes, etc., than standard Amiga drives.

Determining the Current Diskchange Number

You determine the current diskchange number of a disk by passing an IOExtTD to the device with TD_CHANGENUM set in io_Command. For quick I/O, you must set io_Flags to IOF_QUICK.

DiskIO->iotd_Req.io_Flags = IOF_QUICK;
DiskIO->iotd_Req.io_Command = TD_CHANGENUM;
IExec->BeginIO((struct IORequest *)DiskIO);

TD_CHANGENUM returns the current value of the diskchange counter (as used by the enhanced commands) in io_Actual. The disk change counter is incremented each time the disk is inserted or removed.

DiskIO->iotd_Req.io_Flags = IOF_QUICK;
DiskIO->iotd_Req.io_Command = TD_CHANGENUM;
IExec->BeginIO((struct IORequest *)DiskIO);
uint32 change_count = DiskIO->iotd_Req.io_Actual;   /* store current diskchange value */
 
DiskIO->iotd_Req.io_Length = 1;          /* Turn on the drive motor */
DiskIO->iotd_Count = change_count;
DiskIO->iotd_Req.io_Command = ETD_MOTOR;
IExec->DoIO((struct IORequest *)DiskIO);

Commands for Diagnostics and Repair

The trackdisk device provides commands to move the drive heads to a specific track. These commands are provided for internal diagnostics, disk repair, and head cleaning only.

Moving the Drive Head to a Specific Track

You move the drive head to a specific track by passing an IOExtTD to the device with TD_SEEK or ETD_SEEK set in io_Command, and io_Offset set to the byte offset of the track to which the seek is to occur.

DiskIO->iotd_Req.io_Offset = (uint32)(TRACK_SIZE * track);
DiskIO->iotd_Req.io_Command = TD_SEEK;
IExec->DoIO((struct IORequest *)DiskIO);
Seeking is not Reading
TD_SEEK and ETD_SEEK do not verify their position until the next read. That is, they only move the heads; they do not actually read any data.

Notification of Disk Changes

Many programs will wish to be notified if the user has changed the disk in the active drive. While this can be done via the Intuition DISKREMOVED and DISKINSERTED messages, sometimes more tightly controlled testing is required. The trackdisk device provides commands to initiate interrupt processing when disks change.

Those implementing their own disk change notification support in a file system or device driver should review Supporting Disk Change Events.

Adding a Diskchange Software Interrupt Handler

The trackdisk device lets you add a software interrupt handler that will be Cause()’ed when a disk insert or remove occurs. Within the handler, you may only call the status commands that can use IOF_QUICK.

You add a software interrupt handler by passing an IOExtTD to the device with a pointer to an Interrupt structure set in io_Data, the length of the structure set in io_Length and TD_ADDCHANGEINT set in io_Command.

DiskIO->iotd_Req.io_Length  = sizeof(struct Interrupt)
DiskIO->iotd_Req.io_Data    = (APTR)Disk_Interrupt;
DiskIO->iotd_Req.io_Command = TD_ADDCHANGEINT;
IExec->SendIO((struct IORequest *)DiskIO);
Going, going, gone
This command does not return when executed. It holds onto the IORequest until the TD_REMCHANGEINT command is executed with that same IORequest. Hence, you must use SendIO() with this command.

Removing a Diskchange Software Interrupt Handler

You remove a software interrupt handler by passing the same IOExtTD to the device you used with TD_ADDCHANGEINT but with the io_Command set to TD_REMCHANGEINT. You must pass it the same Interrupt structure used to add the handler.

For maximum compatibility, when you remove the change interrupt handler with the TD_REMCHANGEINT command you must make sure that the TD_ADDCHANGEINT command was successful, and if not, then you will have to remove the IOExtTD as shown below:

DiskIO->iotd_Req.io_Command = TD_REMCHANGEINT;
IExec->DoIO((struct IORequest *)DiskIO);
 
/* Was the TD_ADDCHANGEINT command successful? */
if (IExec->CheckIO((struct IORequest *)DiskIO) == NULL)
{
   DiskIO->io_Command = TD_REMCHANGEINT;
   IExec->DoIO((struct IORequest *)DiskIO);
}
else
{
   /* The TD_ADDCHANGEINT command was not successful and has been rejected. */
   IExec->WaitIO((struct IORequest *)DiskIO);
}

Commands for Low-Level Access

The trackdisk device provides commands to read and write raw flux changes on the disk. The data returned from a low-level read or sent via a low-level write should be encoded into some form of legal flux patterns. See the Amiga Hardware Reference Manual and books on magnetic media recording and reading.

Reading Raw Data from a Disk

ETD_RAWREAD and TD_RAWREAD perform a raw read from a track on the disk. They seek to the specified track and read it into the user’s buffer.

No processing of the track is done.

It will appear exactly as the bits come off the disk – typically in some legal flux format (such as MFM, FM, GCR, etc; if you don’t know what these are, you shouldn’t be using this call). Caveat programmer.

This interface is intended for sophisticated programming only. You must fully understand digital magnetic recording to be able to utilize this call. It is also important that you understand that the MFM encoding scheme used by the higher level trackdisk routines may change without notice. Thus, this routine is only really useful for reading and decoding other disks such as MS-DOS formatted disks.

You read raw data from a disk by passing an IOExtTD to the device with TD_RAWREAD or ETD_RAWREAD set in io_Command, the number of bytes to be read set in io_Length (maximum 32K), a pointer to the read buffer set in io_Data, and io_Offset set to the byte offset of the track where you want to the read to begin. For ETD_RAWREAD, you must also set iotd_Count to the current diskchange number.

DiskIO->iotd_Req.io_Length  = 1024;             /* number of bytes to read */
DiskIO->iotd_Req.io_Data    = (APTR)Readbuffer; /* pointer to buffer */
DiskIO->iotd_Req.io_Offset  = (uint32)(TRACK_SIZE * track); /* track number */
DiskIO->iotd_Req.io_Flags   = IOTDF_INDEX       /* Set for index sync */
DiskIO->iotd_Count          = change_count;     /* diskchange number */
DiskIO->iotd_Req.io_Command = ETD_RAWREAD;
IExec->DoIO((struct IORequest *)DiskIO);

A raw read may be synched with the index pulse by setting the IOTDF_INDEXSYNC flag or synched with a $4489 sync pattern by setting the IOTDF_WORDSYNC flag. See the trackdisk.doc in the SDK for more information about these flags.

Forewarned is Forearmed
The AmigaOS development team may make enhancements to the disk format in the future. The AmigaOS development team intends to provide compatibility within the trackdisk device. Anyone who uses these raw routines is bypassing this upward-compatibility and does so at her own risk.

Writing Raw Data to a Disk

ETD_RAWWRITE and TD_RAWWRITE perform a raw write to a track on the disk. They seek to the specified track and write it from the user’s buffer.

No processing of the track is done.

It will be written exactly as the bits come out of the buffer – typically in some legal flux format (such as MFM, FM, GCR; if you don’t know what these are, you shouldn’t be using this call). Caveat Programmer.

This interface is intended for sophisticated programming only. You must fully understand digital magnetic recording to be able to utilize this call. It is also important that you understand that the MFM encoding scheme used by the higher level trackdisk routines may change without notice. Thus, this routine is only really useful for encoding and writing other disk formats such as MS-DOS disks.

You write raw data to a disk by passing an IOExtTD to the device with TD_RAWRITE or ETD_RAWRITE set in io_Command, the number of bytes to be written set in io_Length (maximum 32K), a pointer to the write buffer set in io_Data, and io_Offset set to the byte offset of the track where you want to the write to begin. For ETD_RAWWRITE, you must also set iotd_Count to the current diskchange number.

DiskIO->iotd_Req.io_Length  = 1024;              /* number of bytes to write */
DiskIO->iotd_Req.io_Data    = (APTR)Writebuffer; /* pointer to buffer */
DiskIO->iotd_Req.io_Offset  = (uint32)(TRACK_SIZE * track); /* track number */
DiskIO->iotd_Req.io_Flags   = IOTDF_INDEX        /* Set for index sync */
DiskIO->iotd_Count          = change_count;      /* diskchange number */
DiskIO->iotd_Req.io_Command = ETD_RAWWRITE;
IExec->DoIO((struct IORequest *)DiskIO);

A raw write may be synched with the index pulse by setting the IOTDF_INDEXSYNC flag or synched with a $4489 sync pattern by setting the IOTDF_WORDSYNC flag. See the trackdisk.doc in the SDK for more information about these flags.

Beware The Synch On Write
Synch on write will most likely destroy some of the bits following the synch before reliable writing begins. This is due to how the Paula chip implements this feature. Use synch on write very carefully if at all.

Limitations for Sync’ed Reads and Writes

There is a delay between the index pulse and the start of bits coming in from the drive (e.g. DMA started). It is in the range of 135-200 microseconds. This delay breaks down as follows: 55 microseconds for software interrupt overhead (this is the time from interrupt to the write of the DSKLEN register); 66 microseconds for one horizontal line delay (remember that disk I/O is synchronized with Agnus’ display fetches). The last variable (0-65 microseconds) is an additional scan line since DSKLEN is poked anywhere in the horizontal line. This leaves 15 microseconds unaccounted for. In short, you will almost never get bits within the first 135 microseconds of the index pulse, and may not get it until 200 microseconds. At 4 microsecs/bit, this works out to be between 4 and 7 bytes of user data delay.

Forewarned is Forearmed
The AmigaOS development team may make enhancements to the disk format in the future. The AmigaOS development team intends to provide compatibility within the trackdisk device. Anyone who uses these raw routines is bypassing this upward-compatibility and does so at her own risk.

Trackdisk Device Errors

The trackdisk device returns error codes whenever an operation is attempted.

DiskIO->iotd_Req.io_Length  = TRACK_SIZE;
DiskIO->iotd_Req.io_Data    = (APTR)Writebuffer;
DiskIO->iotd_Req.io_Offset  = (uint32)(TRACK_SIZE * tracknum);
DiskIO->iotd_Count          = change_count;
DiskIO->iotd_Req.io_Command = ETD_WRITE;
if (IExec->DoIO((struct IORequest *)DiskIO))
    IDOS->Printf("ETD_WRITE failed.  Error: %ld\n", DiskIO-iotd.io_Error);

When an error occurs, these error numbers will be returned in the io_Error field of your IOExtTD block.

Trackdisk Device Error Codes
Error Value Explanation
TDERR_NotSpecified 20 Error could not be determined
TDERR_NoSecHdr 21 Could not find sector header
TDERR_BadSecPreamble 22 Error in sector preamble
TDERR_BadSecID 23 Error in sector identifier
TDERR_BadHdrSum 24 Header field has bad checksum
TDERR_BadSecSum 25 Sector data field has bad checksum
TDERR_TooFewSecs 26 Incorrect number of sectors on track
TDERR_BadSecHdr 27 Unable to read sector header
TDERR_WriteProt 28 Disk is write-protected
TDERR_DiskChanged 29 Disk has been changed or is not currently present
TDERR_SeekError 30 While verifying seek position, found seek error
TDERR_NoMem 31 Not enough memory to do this operation
TDERR_BadUnitNum 32 Bad unit number (unit # not attached)
TDERR_BadDriveType 33 Bad drive type (not an Amiga 3.5’’ disk)
TDERR_DriveInUse 34 Drive already in use (only one task exclusive)
TDERR_PostReset 35 User hit reset; awaiting doom

Example Trackdisk Program

/*
 * Track_Copy.c
 *
 * This program does a track by track copy from one drive to another
 *
 * This program will only run from the CLI.  If started from
 * the workbench, it will just exit...
 *
 * Usage:  trackcopy  dfx dfy
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <devices/trackdisk.h>
#include <dos/dosextens.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
#include <stdio.h>
#include <string.h>
 
 
#define TRACK_SIZE      ((int32)(NUMSECS * TD_SECTOR))
 
 
/*
 * Turn the BUSY flag off/on for the drive
 * If onflag is TRUE, the disk will be marked as busy...
 *
 * This is to stop the validator from executing while
 * we are playing with the disks.
 */
VOID disk_busy(CONST_STRPTR drive, int32 onflag)
{
    IDOS->Inhibit(drive, onflag);
}
 
 
/*
 * This turns the motor off
 */
VOID Motor_Off(struct IOExtTD *disk)
{
    disk->iotd_Req.io_Length=0;
    disk->iotd_Req.io_Command=TD_MOTOR;
    IExec->DoIO((struct IORequest *)disk);
}
 
 
/*
 * This turns the motor on
 */
VOID    Motor_On(struct IOExtTD *disk)
{
    disk->iotd_Req.io_Length=1;
    disk->iotd_Req.io_Command=TD_MOTOR;
    IExec->DoIO((struct IORequest *)disk);
}
 
 
/*
 * This reads a track, reporting any errors...
 */
 
int16 Read_Track(struct IOExtTD *disk,UBYTE *buffer, int16 track)
{
int16 All_OK=TRUE;
 
    disk->iotd_Req.io_Length=TRACK_SIZE;
    disk->iotd_Req.io_Data=(APTR)buffer;
    disk->iotd_Req.io_Command=CMD_READ;
    disk->iotd_Req.io_Offset=(uint32)(TRACK_SIZE * track);
    IExec->DoIO((struct IORequest *)disk);
    if (disk->iotd_Req.io_Error)
    {
        All_OK=FALSE;
        IDOS->Printf("Error %lu when reading track %ld",disk->iotd_Req.io_Error,track);
    }
    return(All_OK);
}
 
 
 
/*
 * This writes a track, reporting any errors...
 */
 
int16 Write_Track(struct IOExtTD *disk,UBYTE *buffer,int16 track)
{
int16 All_OK=TRUE;
 
    disk->iotd_Req.io_Length=TRACK_SIZE;
    disk->iotd_Req.io_Data=(APTR)buffer;
    disk->iotd_Req.io_Command=TD_FORMAT;
    disk->iotd_Req.io_Offset=(uint32)(TRACK_SIZE * track);
    IExec->DoIO((struct IORequest *)disk);
    if (disk->iotd_Req.io_Error)
    {
        All_OK=FALSE;
        IDOS->Printf("Error %ld when writing track %ld",disk->iotd_Req.io_Error,track);
    }
    return(All_OK);
}
 
 
 
/*
 * This function finds the number of TRACKS on the device.
 * NOTE That this is TRACKS and not cylinders.  On a Two-Head
 * drive (such as the standard 3.5" drives) the number of tracks
 * is 160, 80 cylinders, 2-heads.
 */
 
int16 FindNumTracks(struct IOExtTD *disk)
{
    disk->iotd_Req.io_Command=TD_GETNUMTRACKS;
    IExec->DoIO((struct IORequest *)disk);
    return((int16)disk->iotd_Req.io_Actual);
}
 
 
 
 
 
 
 
/*
 * This routine allocates the memory for one track and does
 * the copy loop.
 */
 
VOID Do_Copy(struct IOExtTD *diskreq0,struct IOExtTD *diskreq1)
{
UBYTE *buffer;
int16 track;
int16 All_OK;
int16 NumTracks;
 
    if (buffer=IExe->AllocVecTags(TRACK_SIZE, AVT_Type, MEMF_SHARED, TAG_END))
    {
        IDOS->Printf(" Starting Motors\r");
        Motor_On(diskreq0);
        Motor_On(diskreq1);
        All_OK=TRUE;
 
        NumTracks=FindNumTracks(diskreq0);
 
        for (track=0;(track<NumTracks) && All_OK;track++)
        {
            IDOS->Printf(" Reading track %ld\r",track);
 
            if (All_OK=Read_Track(diskreq0,buffer,track))
            {
                IDOS->Printf(" Writing track %ld\r",track);
 
                All_OK=Write_Track(diskreq1,buffer,track);
            }
        }
        if (All_OK) IDOS->Printf(" * Copy complete *");
        IDOS->Printf("\n");
        Motor_Off(diskreq0);
        Motor_Off(diskreq1);
        IExec->FreeVec(buffer);
    }
    else IDOS->Printf("No memory for track buffer...\n");
}
 
 
/*
 * Prompts the user to remove one of the disks.
 * Since this program makes an EXACT copy of the disks
 * AmigaDOS would get confused by them so one must be removed
 * before the validator is let loose.  Also, note that the
 * disks may NEVER be in drives on the SAME computer at the
 * SAME time unless one of the disks is renamed.  This is due
 * to a bug in the system.  It would normally be prevented
 * by a diskcopy program that knew the disk format and modified
 * the creation date by one clock-tick such that the disks would
 * be different.
 */
 
VOID Remove_Disks(VOID)
{
    IDOS->Printf("\nYou *MUST* remove at least one of the disks now.\n");
    IDOS->Printf("\nPress RETURN when ready\n");
    while(getchar()!='\n');
}
 
 
/*
 * Prompts the user to insert the disks.
 */
 
VOID Insert_Disks(char drive1[], char drive2[])
{
    IDOS->Printf("\nPlease insert source disk in %s\n",drive1);
    IDOS->Printf("\n          and destination in %s\n",drive2);
    IDOS->Printf("\nPress RETURN when ready\n");
    while(getchar()!='\n');
}
 
 
/*
 * Open the devices and mark them as busy
 */
VOID Do_OpenDevice(struct IOExtTD *diskreq0,struct IOExtTD *diskreq1, int32 unit[])
{
char drive1[] = "DFx:";  /* String for source drive */
char drive2[] = "DFx:";  /* String for destination drive */
 
    drive1[2] = unit[0]+ '0';  /* Set drive number for source */
 
    if (!IExec->OpenDevice(TD_NAME,unit[0],(struct IORequest *)diskreq0,0L))
    {
          disk_busy(drive1,TRUE);
          drive2[2] = unit[1]+ '0';  /* Set drive number for destination */
 
        if (!IExec->OpenDevice(TD_NAME,unit[1],(struct IORequest *)diskreq1,0L))
        {
            disk_busy(drive2,TRUE);
 
            Insert_Disks(drive1,drive2);
            Do_Copy(diskreq0,diskreq1);
            Remove_Disks();
 
            disk_busy(drive2,FALSE);
            IExec->CloseDevice((struct IORequest *)diskreq1);
        }
        else IDOS->Printf("Could not open %s\n",drive2);
 
        disk_busy(drive1,FALSE);
        IExec->CloseDevice((struct IORequest *)diskreq0);
    }
    else IDOS->Printf("Could not open %s\n",drive1);
}
 
 
int16 ParseArgs(int argc, char **argv, int32 Unit[])
#define OKAY 1
{
int j=1, params = OKAY;
char *position[]={"First","Second"};
 
if (argc != 3)
    {
    IDOS->Printf("\nYou must specify a source and destination disk\n");
    return(!OKAY);
    }
else if (strcmp(argv[1],argv[2]) == 0)
           {
           IDOS->Printf("\nYou must specify different disks for source and destination\n");
           return(!OKAY);
           }
     else while (params == OKAY && j<3)
           {
           if (strnicmp(argv[j],"df",2)==0)
             {
             if (argv[j][2] >= '0' && argv[j][2] <= '3' && argv[j][3] == '\0')
               {
               Unit[j-1] = argv[j][2] - 0x30;
               }
             else
               {
               IDOS->Printf("\n%s parameter is wrong, unit number must be 0-3\n",position[j-1]);
               params = !OKAY;
               return(!OKAY);
               }
             }
           else
             {
             IDOS->Printf("\n%s parameter is wrong, you must specify a floppy device df0 - df3\n",
                     position[j-1]);
             params=!OKAY;
             return(!OKAY);
             }
           j++;
           }
return(OKAY);
}
 
int main(int argc, char **argv)
{
    int32 unit[2];
 
    if (ParseArgs(argc, argv, unit))       /* Check inputs */
    {
        struct MsgPort *diskPort = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
        if (diskPort != NULL)
        {
            struct IOExtTD *diskreq0 = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
                ASOIOR_Size, sizeof(struct IOExtTD),
                ASOIOR_ReplyPort, diskPort,
                TAG_END);
 
            if (diskreq0 != NULL)
            {
                struct IOExtTD *diskreq1 = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
                    ASOIOR_Size, sizeof(struct IOExtTD),
                    ASOIOR_ReplyPort, diskPort,
                    TAG_END);
 
                if (diskreq1 != NULL)
                {
                    Do_OpenDevice(diskreq0,diskreq1, unit);
                    IExec->FreeSysObject(ASOT_IOREQUEST, diskreq1);
                }
                else IDOS->Printf("Out of memory\n");
                IExec->FreeSysObject(ASOT_IOREQUEST, diskreq0);
            }
            else IDOS->Printf("Out of memory\n");
            IExec->FreeSysObject(ASOT_PORT, diskPort);
        }
        else IDOS->Printf("Could not create diskReq port\n");
    }
 
    return 0;
}
Only one per customer
Since this example program makes an exact track-for-track duplicate, AmigaDOS will get confused if both disks are in drives on the system at the same time. While the disks are inhibited, this does not cause a problem, but during normal operation, this will cause a system hang. To prevent this, you can relabel one of the disks. A commercial diskcopy program would have to understand the disk format and either relabel the disk or modify the volume creation date/time by a bit in order to make the disks look different to the system.

Additional Information on the Trackdisk Device

Additional programming information on the trackdisk device can be found in the include files and the autodocs for the trackdisk device. Both are contained in the Autodocs.

Trackdisk Device Information
Includes devices/trackdisk.h
devices/trackdisk.i
Autodocs trackdisk.doc