ADFS IDE Driver Documentation
=============================

History
=======

19-Mar-91  14:58  Christopher Partington (Cambridge Systems Design)
Document created from various notes.

01-May-91  11:52  Christopher Partington (Cambridge Systems Design)
Updated documentation of action on write errors to reflect source changes.
Added comments on disabling drive readahead. 

12-Mar-92  10:18  Christopher Partington (Cambridge Systems Design)
Updated documentation of mount procedure.


Introduction
============

Versions of ADFS upto version 2.06 supported the HD63463 (ST506) Winchester
disc controller and either the 1793 or 1772 floppy disc controllers
according to whether the assembled module was for use in an A500 or not.
For incorporation in RISC OS 2.50, it was decided to add support for the
Chips and Technologies 82C710 peripheral chip which provides a
765-compatible floppy disc controller and an IDE (Integrated Drive
Electronics otherwise known as AT) Winchester disc interface. This document
describes the changes made to accomodate the IDE driver alongside the
existing ST506 driver. Documents elsewhere should describe the addition of
the 765 floppy driver and the manner in which the old and new drivers are
accomodated within ADFS.


General
=======

To save time and to have a high level of confidence in the integrity of
ADFS, it was decided that the ST506 driver should be left essentially
untouched. However, all entry points to the Winchester disc drivers are new.

Unlike the floppy drivers, only one of which is active at a time (according
to the hardware present), both Winchester drivers can be active at the same
time since, even though only one Winchester disc controller is ever present
on the motherboard, a second controller may be added on a podule. If a
controller added on a podule is of the same type as that on the motherboard,
the controller on the podule will be used in preference to that on the
motherboard (this situation already existed with the ST506 driver). Since
both the ST506 and IDE interfaces support two drives, this gives a maximum
of four drives per system (the maximum hard discs allowed by FileCore).

The driver is a rough emulation of the ST506 driver: there is no attempt to
support multiple concurrent commands or command queues as the primary goals
were speed and compatibility. Indeed, tests on an ARM2, 8MHz memory system
have shown that the driver keeps up with drives satisfactorily (i.e. it does
not suffer from additional rotational latency), even if the drive's
readahead is disabled.


Entry Points
============

There are several entry points to the module. Some of these can be handled
directly (e.g. configure) but some must be handled differently by each
driver. At the beginning of the project, some simple changes to the module
entry points were agreed by Christopher Partington of Cambridge Systems
Design (the author of the IDE Winchester driver) and Lawrence Rust of
Cambridge Beacon (the author of the 765 floppy driver). These changes
determine whether the call is to be handled by the Winchester disc driver or
the floppy disc driver (or both) and pass the call on as necessary.
Following these changes, it was possible for the two new drivers to be
developed mostly independently without risk of label name clashes etc. When
it was time to merge the four drivers together, the whole operation only
took about two hours as a consequence. This history may explain why the file
structure is a little unusual and the label names long.

There now follows a description of the main entry points to the Winchester
disc driver. A pseudocode description can be found at the end of the
document.


WinInit
-------

Called to initialise the ST506 and IDE drivers when the module is
initialised by RISC OS. Apart from the driver-specific initialisation, this
entry point establishes a map of logical drive number (4..7) to physical
drive number (0..1) and driver type (ST506/IDE). The IDE driver reads a byte
of battery-backed RAM to determine whether the drives are to be set into
autospindown mode. If this byte is non-zero (it actually contains the delay
in five second units before the drive should spin down), the driver installs
a CallEvery routine to examine the drives and issue autospindown commands to
them when they become ready.


WinDie
------

Called to shut down the ST506 and IDE drivers when the module is killed by
RISC OS. In consists mainly of releasing interrupt devices and vectors.


Service entries
---------------

- WinReset

Called following a reset of the machine. Only on a hard reset will the
initialisation entry point be called before this. A soft reset will call
this entry point only. To cope with both types of reset without having to
check which, this releases vectors without checking for error (it would fail
following a soft reset) and reclaims them.


- WinIDEPoduleDying

This service call should be issued by the module loaded from an IDE podule
when that module is killed. It causes ADFS to release the device vector.


SWI entries
-----------

- DoSwiWinControllerType

Called when the module has received a SWI to determine the controller type
and the logical drive number indicates that the drive is a hard disc (4..7).
It simply reads the drive type from the map established on initialisation.


- DoSwiSetIDEController

Called when the module has received SWI ADFS_SetIDEController. This SWI
informs the IDE driver of the details of an alternative IDE interface.


- DoSwiWinPowerControl

Called to control the power-saving features of the Winchester drivers. If
invoked on drives that do not fully support the power-saving IDE commands,
it can result in those drives being unusable until powered down and up again
(at least two manufacturers' drives).


- DoSwiIDEUserOp

Called to perform an IDE operation not accessible via the ADFS_DiscOp
interface. This interface is expected to be used only by disc repair and
other similarly "dangerous" programs. It does not rely on interrupts from
the drives and so can be used to issue commands which do not raise an
interrupt on completion (e.g. diagnostics).


FileCore entry points
---------------------

- WinMount

This entry point is called following a call from FileCore via the
miscellaneous entry point when the logical drive number indicates that it is
a hard disc (drives 4..7). It calls the low-level entry point to perform a
Winchester boot operation.


- WinLowLevel

This entry point is called following a call from FileCore via the low-level
entry point when the logical drive number indicates that it is a hard disc
(drives 4..7). Values passed in the registers indicate the action required
(e.g. read/write/seek/format). The drive map is referenced to determine
whether the logical disc drive number refers to an ST506 or IDE drive and
the relevant driver is then called with a physical drive number as a
parameter.


IDE - some important facts
==========================

IDE drives are intelligent (Intelligent Drive Electronics) and take on much
of the work of the programmer. They have read/write registers to specify the
starting disc address for a transfer (cylinder/head/sector) and the number
of sectors to be transferred. It is not even necessary for the programmer to
initiate seeks to position the heads: once a read/write command has been
initiated, the drive will seek automatically to the correct cylinder and
start the transfer, updating its registers for each sector, switching heads
and stepping to the next cylinder as tracks are exhausted.

IDE drives are intended for the PC world in which there are predefined drive
types. Because it is not simple to add a new drive type, IDE drives are
designed to be able to be mapped onto one of the standard drive types by
changing their apparent geometry. For example, many of those drive types are
to handle ST506 drives with 17 512-byte sectors per track. An IDE drive can
be "told" that it has 17 sectors per track and 4 heads and it will then
behave as if it really has, appearing to change heads and step when
appropriate. The programmer does not normally need to know what the real
geometry of the drive is, indeed the sectors per track may vary across the
disc in a way that most operating systems cannot handle. When a PC boots,
the drive type is read from battery-backed RAM and the drive is initialised
with the parameters indicated by the type code. The drive continues to
perform with the initialised geometry until it is reset. There is a downside
to this approach: because the drive is continually mapping specified disc
addresses to physical ones, it does not perform at its best in this
"translation mode". Many drives can be made to go faster by using them in
their "native mode" (drives with a variable number of sectors per track do
not have native modes) and it is this mode that those drives should be used
under RISC OS.


IDE driver low-level description
================================

All commands issued to the expect the drive being addressed to report ready
status at the point at which the command is issued. To this end, each drive
has state variables that indicate whether the driver considers it to be
ready for access. Before a drive can be accessed via the usual DiscOp
interface, the following must be true:

 - the drive must be reporting ready i.e. it has passed its internal
   diagnostics and posted "drive ready" at the interface.

 - if autospindown has been configured, the relevant command to set this
   must have been issued to the drive.

 - if the drive does not power up in the logical configuration in which it
   is to be used, the driver must have initialised its parameters.

If the drive state does not indicate that all these are valid, the driver
will wait/issue commands until they are (or an error occurs). Once the drive
state indicates that the drive is ready, the DiscOp is started.

The processing of DiscOps falls into two catagories: those that involve data
transfer and those that do not (verify is the exception to this as no data
is transferred but it is handled like a data transfer operation).


Operations with data transfer (Verify/Read/Write/Format track)
-----------------------------

These are the main operations and the driver is optimised for them. Each of
these DiscOps can be mapped onto a a single IDE opcode that performs the
required operation. No intermediate IDE opcodes (e.g. seek) are required
because of the intelligence of the controller. After preliminary processing,
all of these DiscOps branch to or call a common routine called
WinIDEReadWriteSecs.

WinIDEReadWriteSecs first determines the direction of the transfer (data
in/out) and copies a routine into RAM (for speed) which will later copy the
data to/from the controller. It then calls foreground/background routines
according to the option bits passed with the DiscOp.

All transfers break down into a foreground component and a background
component, either of which can be of zero length. The following table
indicates the action of the driver according to the option bits passed.


Background  Scatter list   R4      Foreground transfer  Background Transfer
     1            1        !0            R4 bytes            Unlimited
     1            1         0            -                   Unlimited
     1            0        !0            R4 bytes            -
     1            0         0            -                   -
     0            1        !0            R4 bytes            -
     0            1         0            -                   -
     0            0        !0            R4 bytes            -
     0            0         0            -                   -

From this follow:
   1. all foreground transfers use R4 as the limit on the transfer;
   2. all background transfers terminate on hitting a zero entry in the
      scatter list as there is no overall limit on the transfer;
   3. some combinations result in no transfer.

The driver assumes that only the final sector of the transfer can be
incomplete. All other sectors are transferred in their entirety.

A single data transfer FileCore discop is broken down as follows:
   - a discop is broken down into a number of lumps;
   - a lump is made up of a number of blocks (= sectors, the last of
     which may not be transferred in its entirety);


Foreground transfers

The lowest-level code always assumes that the transfer address passed points
to a scatter list. As foreground transfers may not provide a scatter list,
the foreground code fakes one. It first calculates the physical start
address for the transfer and then calls the low-level routine
WinIDEStartTransfer to start the operation. It then waits, polling a flag
that is eventually changed under interrupt, before returning the results of
the operation to the caller.


Background transfers

A background transfer is only ever initiated when a scatter list has been
provided (see above). After calculating the physical start address for the
transfer, the background start code starts the operation by calling
WinIDEStartTransfer and then returns to the caller.


Starting a transfer

WinIDEStartTransfer is called by the foreground and background start
routines and also under interrupt when an IDE operation completes (see
later). It is here that ESCAPE is detected and handled (simply by returning
an error to the caller). Since IDE does not provide a "read until I tell you
to stop" and equivalents, the number of sectors to be transferred (the "lump
size") has to be determined before the operation is started. Early versions
of the driver simply looked at the first entry in the scatter list and
started a transfer of that length. This turned out to be fine for transfers
direct from the user and for filing system operations with ADFSBuffers
(read-ahead/write-behind) disabled since they do not use a scatter list and
the transfers are large. It was also fine for reads using a scatter list
because the driver/drive combination is fast enough to start the next
transfer without losing a rotation of the disc. It was hopeless for writes
as a rotation was lost for every entry in the scatter list (1kB). The
solution was obviously to make transfers larger.

It is possible to determine how big a transfer to start by adding entries in
the scatter list. However, the scatter list entries cannot be updated until
it is known that the sectors have been transferred as FileCore will reuse
updated entries. Unfortunately, because IDE drives buffer writes from the
driver, it is not possible for the driver to know that sectors have been
written to the disc until the interrupt for completion of the FINAL sector.
The solution is unpleasant but works. For each transfer, two pointers into
the scatter list are maintained: a pointer to the entry at which the
transfer started and a pointer to the entry currently being processed. Each
scatter list entry is copied so that it can be updated after each sector
without touching the real scatter list. The real scatter list is only
updated on completion of transfer of all sectors making up the IDE transfer.
Obviously this decreases the decoupling between FileCore and ADFS but it
makes writes upto 10 times faster without decreasing read performance
drastically.


Interrupts

Each IDE data transfer operation raises an interrupt on completion of each
block. For read/write/format, the block size is equal to the sector size;
for verify, the block size is the sector size times the number of sectors
i.e. only one interrupt ever occurs for a verify operation. For a
write/format operation, no interrupt is given for the first sector: the
driver has to poll until the controller asserts DRQ and then transfer the
first sector; the controller will interrupt when it is ready to accept the
next sector (this is probably before it has written the previous sector to
disc) and will interrupt at the end of the transfer to indicate that all
sectors have been written to disc. For a read operation, the controller will
interrupt each time a sector can be read by the processor.

When all sectors of a lump have been transferred under interrupt, the driver
interrupt routine determines whether there is more data to transfer and, if
so, starts another transfer using WinIDEStartTransfer. When there is no more
to be transferred, the callback routine for the transfer is called. For
foreground transfers, this updates variables and sets a flag to tell the
foreground poll routine that the operation is complete. For background
transfers, the callback routine calls FileCore to tell it that the operation
is complete.


Read errors

Read errors update the transfer length with the amount of data actually read
from the controller before the error occurred. The updated disc address is
the address of the error as indicated by the controller's registers. 


Verify errors

The controller's registers are used to determine how much data was verified
before the error the transfer length is updated by this amount. This can be
erroneous as some controllers have bugs in this error but there is no
alternative. The updated disc address is the address of the error as
indicated by the controller's registers.


Write errors

Since writes are buffered by the controller, the amount of data written to
the controller is no indication of how much has actually been written to
disc. The transfer length is therefore updated by the difference between the
number of sectors that were requested to be written to disc and the number
of sectors that the controller claims have not been written. This may be
erroneous because of controller bugs but is probably better than not
updating them at all as was previously done (that could lead the caller to
thinking that nothing had been written to disc). The updated disc address is
the address of the error as indicated by the controller's registers. This
may be incorrect because of controller bugs.


Format errors

The transfer length is not updated (only one sector is transferred to format
one whole track) but the updated disc address is the address of the error as
indicated by the controller's registers.


Timeout errors

Any error resulting from a command timeout cannot access the controller
registers as the controller has probably hung. Consequently, the transfer
length and disc address are not updated.


Operations without data transfer (Seek/Restore/Specify)
--------------------------------

These operations are polled because they never take place in background and 
because they are called rarely and so can be polled to speed up the
interrupt routine for the data transfer operations. The controller registers
are written and the driver then waits, polling for a controller interrupt or
a timeout.


Command timeouts
================

All commands are timed out. The timeouts are extremely generous to allow
time for a drive that has been spun down by its controller (due to
inactivity) to spin up again. This is necessary as there is no trivial and
standard way of determining whether a drive is spinning or not (the
controller status bit indicating drive ready is required to be active even
if the controller has spun the drive down). The latest CAM specification
(2.1) documents a command to determine the spin state of a drive and this is
used by the PowerControl SWI to determine the drive's state. This is not a
mandatory command and, at the time of writing, few manufacturers have
implemented it. Since timeout errors should occur only when the drives have
not been correctly set up (or some other hardware problem), the long
timeouts should not be a problem.


Interrupts
==========

There are several conditions that must exist before the IDE driver receives
an interrupt:

   a. the controller must assert IRQ14;
   b. IEN must be enabled in the controller digital output register;
   c. the interrupt must be enabled in IOC (or on the podule);
   d. the CPU must have IRQs enabled.

Condition (b) is always true except during the handling of an interrupt from
the controller. In order to maintain a low interrupt latency, the driver
enables CPU IRQs (d) in its interrupt handler. If the interrupt were not
masked during this time, the controller could interrupt the interrupt
handler which is not designed to be reentrant. (As an example, consider a
multi-sector read: after the interrupt for the first sector, the driver
reads the sector data and the controller interrupts again immediately
because it has cached the next sector.) Option (b) has been chosen instead
of (c) because it requires fewer instructions. Interrupts *are* disabled in
IOC (or on the podule) when no IDE command is active.


Retries
=======

The IDE driver has no code to retry data transfer operations that fail. This
is because the IDE drive controller performs retries itself. Whilst the
controller retries *could* be disabled and the driver perform them instead,
the controller can do much more effective retries e.g. most will move the
head slightly off track as part of their retry strategy.


CAM Specification
=================

Towards the end of development, we received version 2.1 of the CAM
specification for AT (IDE) drives. This indicates that many of the
non-standard features of early drives are now standardised (e.g.
power-saving commands). It also lays down timing constraints, some of which
are extremely generous in favour of the controller rather than the driver,
for example:

   a. 30 seconds spinup time;
   b. 700 microseconds from issue of write command to drive asserting DRQ.
   c. 400 nanoseconds between busy bit in status register being cleared and
      other status bits being valid.

These timings have been incorporated into the driver.


IDE Commands used
=================

See the drive requirement specification.


Formatting of IDE drives
========================

During development, the ST506 formatter program, HForm, was modified to
support IDE drives as well as ST506 drives. It is anticipated that a new and
more friendly formatter program will be written before release: the modified
HForm should help with this.


Defect handling
---------------

A track on an IDE drive is formatted by setting up a 512 byte buffer and
issuing a "format track" command. That buffer contains, for each sector on
the track, a byte to specify how that sector should be formatted. All IDE
drives are supposed to support "format sector good" (specifier &00) and
"format sector bad" (specifier &80). Formatting a sector bad will cause the
controller to return a "bad block" error when that sector is accessed and is
a useful way of forcing a flaky sector to return a hard error. An exception
to this is the Sony SRD3040C which maps "format sector bad" onto "format
sector alternate". This is perverse: a sector formatted bad is always to be
avoided; a sector formatted alternate is still usable.

Many recent IDE drives also support "format sector alternate" (specifier
&40) which causes a spare sector to be used whenever that particular sector
is accessed (this is similar to the way SCSI drives handle defects). This
would be a good thing for a new formatter to use but will require input from
the user as there is no way to determine whether a drive supports this or
not, other than by reading the manual. However, the "format sector
alternate" feature has not been tested on any of the drives used during
development. The current version of HForm only uses "format sector good":
bad sectors are flagged as defects in the defect list written to the drive
but are not marked as bad blocks using "format sector bad".

There is no defect skipping built in to the IDE driver. Consequently, IDE
drives should not be formatted with the "old map" structure. (This is
compatible with SCSIFS.)


Interleave
----------

The interleave of sectors on a track is supposed to be set by the order in
which the sector numbers are placed in the buffer supplied to the controller
at format time (see above). The vast majority of drives examined during
development of the driver (all bar one, I think) have the interleave fixed
at 1:1 and the order of sectors in the buffer is not relevant. Those drives
on which the interleave *can* be changed should be formatted with an
interleave of 1:1 i.e. sector numbers should run consecutively.


Use of HForm
------------

The current version of HForm issues an IDENTIFY command to the drive to try
to read the drive's geometry (this command is optional). It then reads the
drive to see if it has been formatted before and, if so, tries to match its
shape against drives it knows about (just as for ST506 drives). The user can
then accept these values or enter new ones. If the IDENTIFY command
succeeded, the values returned will be offered to the user if "OTHER" is
selected as the drive type. I would suggest that the table of known drives
incorporated in HForm be modified to include only those drives that
either don't support IDENTIFY or that require parameters other than the ones
returned by IDENTIFY, either because they are faster in a particular mode or
because the default values are no good. For example, the Conner CP3044
(Sierra) drive reports 40 sectors per track, 2 heads, 1053 cylinders (unless
it has 2.46 or later firmware and the relevant feature bit in the drive has
been set) but cannot be used with these parameters and, indeed, does not
powerup with this geometry. Consequently, it should have a table entry
specifying the mode in which it *does* powerup (40/4/526).

One of the parameters that HForm requires for IDE drives is to work around
problems in different manufacturers' drives. If the "Drive parameter init
flag" is set to 1, the driver will issue INITIALISE DRIVE PARAMETERS to the
drive when mounted (this is consistent with what happens in the PC world).
If the flag is set to 0, INITIALISE DRIVE PARAMETERS is not used. In
general, it is safe and best to use the flag set to 1 but some drives (e.g.
those that write to EEPROM every time they receive this command) should have
it set to 0. One drive that should have this flag set to 0 is the Conner
CP2044 (Pancho) 2.5 inch drive with 0.83 firmware which loses a platter if
it it initialised with 38 sectors per track and 4 heads (cylinders drop from
552 to 411).

If a drive is to be used in any mode other than the one in which it powers
up (most power up in translate mode but are faster in native mode), the flag
should be set to 1.

HForm has been modified so that a drive can be initialised (have its
filing-system structure written on it) without formatting it. Some IDE
drives are *very* slow to format and, provided that the state of the drive
is known (i.e. it has all its sectors flagged as good by a previous format),
initialisation is usually sufficient. The format speed of a drive can vary
dramatically depending on the geometry selected for the format (e.g.
PrairieTek).


Known problems/bugs
===================

- Both the floppy and Winchester disc drivers should use Service_PreReset to
reset their controllers. Neither does. This should not generally cause
problems.

- A sequence which changes the currently selected IDE drive can cause the
access light of the previous drive to flicker e.g. *Verify 4, *Verify 5
causes the access light of drive 4 to flicker briefly when the verify of
drive 5 starts (*Verify 5, *Verify 4 similarly - it is not just the master
light flickering). It may be possible to select the alternate controller
without polling the status of the current controller but I cannot find
confirmation of this. Consequently, I poll the current drive for not busy,
select the new drive, poll for that being not busy and ready and only then
issue the command.

- Even if the current IDE podule does not need the RAM-based transfer code,
it is still installed. Checking for this case and not installing adds
(slight) overheads to handling of motherboard controller.

- DoSwiPowerControl does not wait for busy to be deasserted before accessing
drive. This can lead to errors being returned if the drive is accessed via
this interface immediately after powerup.


Pseudocode Description
======================

There now follows a (C-like) pseudocode description of the key routines of
the driver. The driver was originally coded in pseudocode before being
written in assembler.

;---------------------------------------------------------------------

WinInit
-------
Initialise variables

if (ST506HardDiscs > 0)
{
   Load regs to point to motherboard ST506
   Issue ServiceCall to find podule ST506
   Save ST506 hardware addresses
   Claim ST506 IRQs
}

if (IDEHardDiscs > 0)
{
   Claim TickerV
   Load regs to point to motherboard IDE
   Issue Service call to find podule IDE
   Save IDE hardware addresses
   if (motherboard IDE || service call claimed)
   {
      Claim IDE IRQs
      Enable IRQ in IDE drives // I assume this affects both drives
   }
   Read spin down parameter from CMOS
   if (spindown != 0)
      Install PowerSetup call every routine to initialise drive spindown
}

Sort out map of logical drives to driver + physical drive

Read approx map sizes from CMOS amd save them

return ST506HardDiscs + IDEHardDiscs

;---------------------------------------------------------------------

WinDie
------
if (ST506HardDiscs > 0)
   Release ST506 IRQs

if (IDEHardDiscs > 0)
{
   Release IDE IRQs
   Release TickerV
   Remove PowerSetup call every routine
}

;---------------------------------------------------------------------

WinReset
--------
Flag no ST506 drive specified
if (ST506HardDiscs > 0)
{
   Release ST506 IRQs
   Claim ST506 IRQs
}

if (IDEHardDiscs > 0)
{
   // IDE system has been reset

   Reset drive state variables
   Release TickerV
   Claim TickerV

   if (own IDE IRQ)
   {
      Release IDE IRQs
      Claim IDE IRQs
   }
}
   
;---------------------------------------------------------------------

Service WinIDEPoduleDying
-------------------------
if (IDEHardDiscs > 0)
{
   Release IDE IRQs
}

;---------------------------------------------------------------------

WinLowLevel
-----------
Update map size held in CMOS
if (ST506 drive)
   call WinLowLevelST506
else
   call WinLowLevelIDE

;---------------------------------------------------------------------

WinMount
--------
if (ST506 drive)
{
   Flag no ST506 drive specified
   call WinLowLevelST506
}
else
{
   call WinLowLevelIDE to read boot block
   validate boot block
   if (valid && init_flag & 1)
      specify()
}

;---------------------------------------------------------------------

SWI WinControllerType
---------------------
Check drive valid
return controller type from drive map

;---------------------------------------------------------------------

SWI SetIDEController
--------------------

if (IDEHardDiscs > 0)
{
   Release IDE IRQs // release current device
   Save hardware addresses
   Claim IDE IRQs
   Enable IRQ in IDE drives // I assume this affects both drives
   Reset drive state variables
}
   
;---------------------------------------------------------------------

SWI PowerControl
----------------
if (drive is IDE && have controller)
{
   Select drive
   switch (reason)
   {
      case read spin state:
         PollCommand(CheckPower)
         return Reg[SecCount]
         break;

      case set autospindown:
         PollCommand(Idle, parm)
         return old value
         break;

      case control drive:
         if (spin down)
            PollCommand(Standby, parm)
         else
            PollCommand(Idle, parm)
         break;
}
else
   error

;---------------------------------------------------------------------

SWI UserOp
----------
if (do not have controller ||
   command active ||
   parameter block address invalid)
   error
else
{
   Enable CPU IRQs
   if (cmd reset)
   {
      Reset drives
      wait (!busy)
   }
   else
   {
      if (data transfer requested)
      {
         Install RAM data transfer code
         Validate buffer address
      }
      commandDisc()
      loop
      {
         wait (!busy || timeout)
         if (!error)
         {
            if (data transfer requested && buffer length > 0)
            {
               wait for DRQ
               if (DRQ)
                  call data transfer code
               else
                  break;
            }
            else
               break;
         }
      } endLoop
   }
   return regs
}

;---------------------------------------------------------------------

WinLowLevelIDE
--------------
Save drive number
Save FileCoreOp
if (do not have controller)
   error
else
{
   sector align disc address and save
   save drive attributes
   save disc size
   if (driveState != active)
      InitDrive
   switch(op)
   reload regs
   sort out disc error number
}

;---------------------------------------------------------------------

InitDrive
---------

sort out timeout according to drive state

wait for drive to become ready

if (driveState == reset)
   if (drivePowerState != 0)
   {
      issue idle command
      wait drive ready
   }

driveState = active

;---------------------------------------------------------------------

PowerSetup // called every second
----------

if (we are still active &&
    no command currently active &&
    IDE hardware present)
{
   for (each configured drive)
   {
      if (driveState == reset)
      {
         if (controller !busy)
         {
            select drive
            if (drive ready)
            {
               issue idle command
               driveState = idled
            }
         }
      }
   }

   if (no reset drives found)
   {
      // all done
      add callback to remove this ticker routine
      flag us as not active so don't add callback again
   }
}

;---------------------------------------------------------------------

op_verify
---------
ReadWriteOp(verify)

;---------------------------------------------------------------------

op_format
---------
if (background || scatterList || length != 1 sector)
   error
else
   ReadWriteOp(format)

;---------------------------------------------------------------------

op_seek
-------
Convert logical disc address to physical disc address
PollCommand(seek)

;---------------------------------------------------------------------

op_restore
----------
Set drive
PollCommand(restore)

;---------------------------------------------------------------------

op_specify
----------
Set drive and head
PollCommand(InitParms)

;---------------------------------------------------------------------

op_write
--------
ReadWriteOp(write)

;---------------------------------------------------------------------

op_read
-------
ReadWriteOp(read)

;---------------------------------------------------------------------

ReadWriteOp
-----------
Install RAM data transfer code
if (background == 0 OR scatter_list == 0 OR R4 != 0)
{
   DoForeground
}

if (background == 1 AND scatter_list == 1)
{
   DoBackground
}

;---------------------------------------------------------------------

DoForeground
------------
if (R4 == 0)
   return OK

if (!scatter_list)
{
   fake_scatter_list.adr = buf_address
   fake_scatter_list.len = buf_len
   scatter_list_ptr = &fake_scatter_list
}
else
{
   while (scatter_list_ptr->adr < 0)
      scatter_list_ptr += scatter_list_ptr->adr

   if (scatter_list_ptr->len == 0)
      return OK
}
opDone = FALSE
CallbackPtr = &CallbackFg
limit = MIN(length_requested, bytes_to_end_of_disc)
calculate physical address for start of transfer
startTransfer(scatter_list_ptr, limit)
enable IRQs
repeatUntil (opDone)
wait for transfer to complete under interrupt
reload registers for return
if (scatter_list == 1 AND scatter_list_ptr->len == 0)
   scatter_list_ptr++
return

;---------------------------------------------------------------------

DoBackground // always terminated by null scatter list entry
------------
while (scatter_list_ptr->adr < 0)
   scatter_list_ptr += scatter_list_ptr->adr

if (scatter_list_ptr->len == 0)
   return OK

limit = bytes_to_end_of_disc
CallbackPtr = &CallbackBg
calculate physical address for start of transfer
StartTransfer(scatter_list_ptr, limit)
if (error starting transfer)
   CallbackBg()
return

;---------------------------------------------------------------------

StartTransfer(limit) // called only for read/write/verify
--------------------
// scatter_list_ptr points to either a real scatter list or a faked one

save parameters
if (escapable && ESCAPE)
   return error

// save current position in scatter list and first scatter list
// entry as no update until while IDE command done

tmp_sptr = scatter_list_ptr
tmp_scatter.adr = tmp_sptr->adr
tmp_scatter.len = tmp_sptr->len

// see how much there is in the scatter list

p = scatter_list_ptr
xfer = 0
loop
{
   while (p->adr < 0)
      p += p->adr

   if (p->len == 0)
      break;

   xfer += p->len
   if (xfer < limit)
   {
      p += 8
      continue
   }
   else
      break;
}
endLoop

xfer = MIN(xfer, limit)

xfer = MIN(xfer, 255 sectors)

lumpSize = xfer
saveLumpSize = xfer

blockSize = MIN(lumpsize, sectorSize)

if (verifyOp)
   blockSize = lumpSize

if (formatOp)
   sectors = sectors per track
else
   sectors = lumpSize rounded up to sector boundary

StartOp
if (error)
   return error

start_timer(&Timeout)
enable interrupt from IDE

if (!(writeOp || formatOp)
   return OK

wait for DRQ to be asserted

if (no DRQ)
   return error

write first sector

;---------------------------------------------------------------------

IRQ // entered only from read/write/verify
---

change to SVC mode

inIRQ = TRUE
mask interrupt in IDE drive

read status // clears IRQ

if (error)
   goto IRQerror1

if (readOp)
{
   if (no DRQ)
      goto IRQerror2

   read(blockSize)
}

   tmp_scatter.adr += blockSize
   tmp_scatter.len -= blockSize
   lumpSize -= blockSize
   limit -= blockSize
   if (lumpSize > 0)
   {
      // more to do in this transfer
      if (tmp_scatter.len <= 0)
      {
         // scatter list entry exhausted - step to next

         tmp_scatter_ptr += 8
         while (tmp_scatter_ptr->adr < 0)
            tmp_scatter_ptr += tmp_scatter_ptr->adr

         // no need to check length as did when starting xfer
         // copy scatter list entry

         tmp_scatter.adr = tmp_scatter_ptr->adr
         tmp_scatter.len = tmp_scatter_ptr->len
      }

      blockSize = MIN(lumpSize, sectorSize)

      if (writeOp)
      {
         if (no DRQ)
            goto IRQerror2

         write(blockSize)
      }
      goto IRQexit
   }
   else
   {
      // end of lump: have hit one of
      //    a) overall limit on transfer
      //    b) amount in scatter list when op started
      //    c) 255 sectors

      logical address += SaveLumpSize

      // update REAL scatter list with data transferred

      UpdateScatterList(scatter_list_ptr, SaveLumpSize)

      if (limit <= 0)
         goto endOfTransfer
      else
      {
         if (scatter_list_ptr->len <= 0)
         {
            while (scatter_list_ptr->adr < 0)
               scatter_list_ptr += scatter_list_ptr->adr

            if (scatter_list_ptr->len == 0)
               goto endOfTransfer
         }
         calculate physical address for start of transfer
         startTransfer(scatter_list_ptr, limit)
         if (error)
            goto endOfTransfer
         else
            goto IRQexit
      }
 

IRQerror1:      // drive status wrong

   decode drive status into error code
   if (verifyOp)
   {
      xfer = lumpSize - sectors not verified * sector size
      UpdateScatterList(scatter_list_ptr, xfer)
   }

IRQerror2:      // no DRQ for data transfer

   convert IDE registers to logical address

IRQerror3:
   if (readOp)
   {
      xfer = saveLumpSize - lumpSize
      UpdateScatterList(scatter_list_ptr, xfer)
   }

   reset drives to put light out


endOfTransfer:

   disable interrupt from IDE
   call callback routine

IRQexit:

   inIRQ = FALSE
   clear masked interrupt in IDE drive
   return

;---------------------------------------------------------------------

Timeout
-------

if (readOp)
   disc address = disc address + saveLumpSize - lumpSize

goto IRQerror3

;---------------------------------------------------------------------

CallbackFg // called when whole op has finished
----------
save results
cancel timer
opDone = TRUE

;---------------------------------------------------------------------

CallbackBg // called when whole op has finished
----------
cancel timer
write process block (scatter list header)
call FileCore

;---------------------------------------------------------------------

*End of document*
