@DATABASE Libraries Manual
@NODE MAIN "Amiga RKM Libraries: 19 Exec Device I/O"
@INDEX Lib_Index/MAIN
@TOC Libraries_Manual/Exec
The Amiga system devices are software engines that provide access to the
Amiga hardware.   Through these devices, a programmer can operate a modem,
spin a disk drive motor, time an event, and blast a trumpet sound in
stereo.  Yet, for all that variety, the programmer uses each device in the
same manner.

@{" What is a Device? " link 19-1}   @{" Using A Device " link 19-3}          @{" Using An Exec Device " link ADCD_v1.2:Reference_Library/Libraries/Lib_Examples/DeviceUse.c/MAIN}
@{" Accessing a Device " link 19-2}  @{" Devices With Functions " link 19-4}  @{" Function Reference " link 19-6}

@ENDNODE

@NODE 19-1 "19 Exec Device I/O / What is a Device?"
An Amiga device is a software module that accepts commands and data and
performs I/O operations based on the commands it receives.  In most cases,
it interacts with either internal or external hardware, (the exceptions
are the clipboard device and ramdrive device which simply use memory).
Generally, an Amiga device runs as a separate task which is capable of
processing your commands while your application attends to other things.


                  Table 19-1: Amiga System Devices

    Amiga Device  Purpose
    ------------  -------
    @{"Audio" link ADCD_v1.2:Reference_Library/Devices/Dev_2/MAIN}         Controls the use of the audio hardware.
    @{"Clipboard" link ADCD_v1.2:Reference_Library/Devices/Dev_3/MAIN}     Manages the cutting and pasting of common data blocks
    @{"Console" link ADCD_v1.2:Reference_Library/Devices/Dev_4/MAIN}       Provides the line-oriented user interface.
    @{"Gameport" link ADCD_v1.2:Reference_Library/Devices/Dev_5/MAIN}      Controls the two mouse/joystick ports.
    @{"Input" link ADCD_v1.2:Reference_Library/Devices/Dev_6/MAIN}         Processes input from the gameport and keyboard devices.
    @{"Keyboard" link ADCD_v1.2:Reference_Library/Devices/Dev_7/MAIN}      Controls the keyboard.
    @{"Narrator" link ADCD_v1.2:Reference_Library/Devices/Dev_8/MAIN}      Produces the Amiga synthesized speech.
    @{"Parallel" link ADCD_v1.2:Reference_Library/Devices/Dev_9/MAIN}      Controls the parallel port.
    @{"Printer" link ADCD_v1.2:Reference_Library/Devices/Dev_10/MAIN}       Converts a standard set of printer control codes to
                  printer specific codes.
    @{"SCSI" link ADCD_v1.2:Reference_Library/Devices/Dev_11/MAIN}          Controls the Small Computer Standard Interface hardware.
    @{"Serial" link ADCD_v1.2:Reference_Library/Devices/Dev_12/MAIN}        Controls the serial port.
    @{"Timer" link ADCD_v1.2:Reference_Library/Devices/Dev_13/MAIN}         Provides timing functions to measure time intervals and
                  send interrupts.
    @{"Trackdisk" link ADCD_v1.2:Reference_Library/Devices/Dev_14/MAIN}     Controls the Amiga floppy disk drives.


The philosophy behind the devices is that I/O operations should be
consistent and uniform.  You print a file in the same manner as you play
an audio sample, i.e., you send the device in question a @{"WRITE" link 19-3 40} command and
the address of the buffer holding the data you wish to write.

The result is that the interface presented to the programmer is
essentially device independent and accessible from any computer language.
This greatly expands the power the Amiga brings to the programmer and,
ultimately, to the user.

Devices support two types of commands: Exec standard commands like @{"READ" link 19-3 40}
and @{"WRITE" link 19-3 40}, and device specific commands like the trackdisk device @{"MOTOR" link 19-3 68}
command which controls the floppy drive motor, and the keyboard device
@{"READMATRIX" link 19-3 59} command which returns the state of each key on the keyboard.
You should keep in mind, however, that supporting standard commands does
not mean that all devices execute them in exactly the same manner.

This chapter contains an introduction to the Exec and amiga.lib functions
that are used when accessing Amiga devices.  Consult the Amiga ROM Kernel
Manual: @{"Devices" link Devices_Manual/MAIN} volume for chapters on each of the Amiga devices and the
commands they support.  In addition, the Amiga ROM Kernel Reference
Manual: Includes and Autodocs contains Autodocs summarizing the commands
of each device, and listings of the device include files.  Both are very
useful manuals to have around when you are programming the devices.

@ENDNODE

@NODE 19-2 "19 Exec Device I/O / Accessing a Device"
Accessing a device requires obtaining a message port, allocating memory
for a specialized message packet called an I/O request, setting a pointer
to the message port in the I/O request, and finally, establishing the link
to the device itself by opening it. An @{"example" link ADCD_v1.2:Reference_Library/Libraries/lib_examples/DeviceUse.c/MAIN} of how to do this will be
provided later in this chapter.

@{" Creating a Message Port " link 19-2-1}  @{" Creating an I/O Request " link 19-2-2}  @{" Opening a Device " link 19-2-3}

@ENDNODE

@NODE 19-2-1 "19 / Accessing a Device / Creating a Message Port"
When a device completes the command in a message, it will return the
message to the message port specifed as the reply port in the message.  A
message port is obtained by calling the @{"CreateMsgPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-1 39} or @{"CreatePort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-1}
function.  You must delete the message port when you are finished by
calling the @{"DeleteMsgPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-2 26} or @{"DeletePort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-2} function.

If your application needs to be compatible with pre-V36 versions of the
operating system, use the amiga.lib functions @{"CreatePort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-1} and
@{"DeletePort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-2}; if you require V36 or higher, you may use the Exec ROM
functions @{"CreateMsgPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-1 39} and @{"DeleteMsgPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-2 26}.

@ENDNODE

@NODE 19-2-2 "19 / Accessing a Device / Creating an I/O Request"
The I/O request is used to send commands and data from your application to
the device.  The I/O request consists of fields used to hold the command
you wish to execute and any parameters it requires.  You set up the fields
with the appropriate information and send it to the device by using Exec
I/O functions.  Different Amiga devices often require different I/O
request structures.  These structures all start with a simple @{"IORequest" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 19} or
@{"IoStdReq" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} structure (see <exec/@{"io.h" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN}>) which may be followed by various
device-specific fields.  Consult the Autodoc and include file for each
device to determine the type and size I/O request required to access the
device.

I/O request structures are commonly created and deleted with the amiga.lib
functions @{"CreateExtIO()" link ADCD_v1.2:Reference_Library/Libraries/Lib_A/A-1-1 6} with @{"DeleteExtIO()" link ADCD_v1.2:Reference_Library/Libraries/Lib_A/A-1-1 6}. These amiga.lib functions are
compatible with Release 2 and previous versions of the operating system.
Applications that already require V37 for other reasons may instead use
the new V37 ROM Exec functions @{"CreateIORequest()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/CreateIORequest()} and @{"DeleteIORequest()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/DeleteIORequest()}.
Any size and type of I/O request may be created with these functions.

Alternately, I/O requests can be created by declaring them as structures
initialized to zero, or by dynamically allocating cleared public memory
for them, but in these cases you will be responsible for the @{"IORequest" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 19}
structure initializations which are normally handled by the above
functions.  The message port pointer in the I/O request tells the device
where to respond with messages for your application.  You must set a
pointer to the message port in the I/O request if you declare it as a
structure or allocate memory for it using @{"AllocMem()" link ADCD_v1.2:Reference_Library/Libraries/Lib_20/20-1}.

@ENDNODE

@NODE 19-2-3 "19 / Accessing a Device / Opening a Device"
The device is opened by calling the @{"OpenDevice()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/OpenDevice()} function.  In addition to
establishing the link to the device, OpenDevice() also initializes fields
in the I/O request.  OpenDevice() has this format:

    return = OpenDevice(device_name,
                        unit_number,
      			(struct IORequest *)IORequest,
                        flags)

  * device_name is one of the following NULL-terminated strings for
    system devices:

        @{"Audio" link ADCD_v1.2:Reference_Library/Devices/Dev_2/MAIN}.device     @{"Parallel" link ADCD_v1.2:Reference_Library/Devices/Dev_9/MAIN}.device  @{"Clipboard" link ADCD_v1.2:Reference_Library/Devices/Dev_3/MAIN}.device
        @{"Printer" link ADCD_v1.2:Reference_Library/Devices/Dev_10/MAIN}.device   @{"Console" link ADCD_v1.2:Reference_Library/Devices/Dev_4/MAIN}.device   @{"scsi" link ADCD_v1.2:Reference_Library/Devices/Dev_11/MAIN}.device
        @{"Gameport" link ADCD_v1.2:Reference_Library/Devices/Dev_5/MAIN}.device  @{"Serial" link ADCD_v1.2:Reference_Library/Devices/Dev_12/MAIN}.device    @{"Input" link ADCD_v1.2:Reference_Library/Devices/Dev_6/MAIN}.device
        @{"Timer" link ADCD_v1.2:Reference_Library/Devices/Dev_13/MAIN}.device     @{"Keyboard" link ADCD_v1.2:Reference_Library/Devices/Dev_7/MAIN}.device  @{"Trackdisk" link ADCD_v1.2:Reference_Library/Devices/Dev_14/MAIN}.device
                         @{"Narrator" link ADCD_v1.2:Reference_Library/Devices/Dev_8/MAIN}.device

  * unit_number is refers to one of the logical units of the device.
    Devices with one unit always use unit 0.  Multiple unit devices like
    the trackdisk device and the timer device use the different units for
    specific purposes.

  * @{"IORequest" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 19} is the structure @{"discussed above" link 19-2-2}.  Some of the devices have
    their own I/O requests defined in their include files and others use
    standard I/O requests, (@{"IOStdReq" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28}).  Refer to the Amiga ROM Kernel
    Reference Manual: @{"Devices" link Devices_Manual/MAIN} for more information.

  * flags are bits set to indicate options for some of the devices. This
    field is set to zero for devices which don't accept options when they
    are opened.  The flags for each device are explained in the Amiga ROM
    Kernel Reference Manual: @{"Devices" link Devices_Manual/MAIN}.

  * return is an indication of whether the @{"OpenDevice()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/OpenDevice()} was successful
    with zero indicating success.  Never assume that a device will
    successfully open.  Check the return value and act accordingly.

    Zero Equals Success for @{"OpenDevice()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/OpenDevice()}.
    -------------------------------------
    Unlike most Amiga system functions, OpenDevice() returns zero for
    success and a device-specific error value for failure.

@ENDNODE

@NODE 19-3 "19 Exec Device I/O / Using a Device"
Once a device has been opened, you use it by passing the I/O request to
it.  When the device processes the I/O request, it acts on the information
the I/O request contains and returns a reply message, i.e., the I/O
request, to the message port when it is finished.  The I/O request is
passed to a device using one of three functions, @{"DoIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/DoIO()}, @{"SendIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/SendIO()} and
@{"BeginIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/amiga_lib/BeginIO()}.  They take only one argument: the I/O request you wish to pass
to the device.

  * @{"DoIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/DoIO()} is a synchronous function.  It will not return until the
    device has finished with the I/O request.  DoIO() will wait, if
    necessary, for the request to complete, and will remove (@{"GetMsg()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-2-3})
    any reply message from the message port.

  * @{"SendIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/SendIO()} is an asynchronous function.  It can return immediately, but
    the I/O operation it initiates may take a short or long time. SendIO
    is normally used when your application has other work to do while the
    I/O request is being acted upon, or if your application wishes to
    allow the user to cancel the I/O. Using SendIO() requires that you
    wait on or check for completion of the request, and remove the
    completed request from the message port with @{"GetMsg()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-2-3}.

  * @{"BeginIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/amiga_lib/BeginIO()} is commonly used to control the QuickIO bit when sending an
    I/O request to a device.  When the QuickIO flag (@{"IOF_QUICK" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 45}) is set in
    the I/O request, a device is allowed to take certain shortcuts in
    performing and completing a request.  If the request can complete
    immediately, the device will not return a reply message and the
    QuickIO flag will remain set.  If the request cannot be completed
    immediately, the QUICK_IO flag will be clear.  @{"DoIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/DoIO()} normally
    requests QuickIO; @{"SendIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/SendIO()} does not.

An I/O request typically has three fields set for every command sent to a
device.  You set the command itself in the @{"io_Command" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} field, a pointer to
the data for the command in the @{"io_Data" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} field, and the length of the data
in the @{"io_Length" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} field.

    SerialIO->IOSer.io_Length   = sizeof(ReadBuffer);
    SerialIO->IOSer.io_Data     = ReadBuffer;
    SerialIO->IOSer.io_Command  = CMD_READ;
    SendIO((struct IORequest *)SerialIO);

Commands consist of two parts (separated by an underscore, all in upper
case): a prefix and the command word.  The prefix indicates whether the
command is an Exec or device specific command.  All Exec standard commands
have "CMD" as the prefix.  They are defined in the include file
<exec/@{"io.h" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 49}>.

             Table 19-2: Standard Exec Device Commands

            CMD_READ   CMD_START  CMD_UPDATE  CMD_CLEAR
            CMD_WRITE  CMD_STOP   CMD_FLUSH   CMD_RESET


You should not assume that a device supports all standard Exec device
commands.  Always check the documentation before attempting to use one of
them.  Device-specific command prefixes vary with the device.


             Table 19-3: System Device Command Prefixes

        Device     Prefix                       Example
        ------     ------                       -------
        @{"Audio" link ADCD_v1.2:Reference_Library/Devices/Dev_2/MAIN}      @{"ADCMD" link ADCD_v1.2:Inc&AD2.1/includes/devices/audio.h/MAIN 25}                        ADCMD_ALLOCATE
        @{"Clipboard" link ADCD_v1.2:Reference_Library/Devices/Dev_3/MAIN}  @{"CBD" link ADCD_v1.2:Inc&AD2.1/includes/devices/clipboard.h/MAIN 27}                          CBD_POST
        @{"Console" link ADCD_v1.2:Reference_Library/Devices/Dev_4/MAIN}    @{"CD" link ADCD_v1.2:Inc&AD2.1/includes/devices/console.h/MAIN 22}                           CD_ASKKEYMAP
        @{"Gameport" link ADCD_v1.2:Reference_Library/Devices/Dev_5/MAIN}   @{"GPD" link ADCD_v1.2:Inc&AD2.1/includes/devices/gameport.h/MAIN 22}                          GPD_SETCTYPE
        @{"Input" link ADCD_v1.2:Reference_Library/Devices/Dev_6/MAIN}      @{"IND" link ADCD_v1.2:Inc&AD2.1/includes/devices/input.h/MAIN 18}                          IND_SETMPORT
        @{"Keyboard" link ADCD_v1.2:Reference_Library/Devices/Dev_7/MAIN}   @{"KBD" link ADCD_v1.2:Inc&AD2.1/includes/devices/keyboard.h/MAIN 18}                          KBD_READMATRIX
        @{"Narrator" link ADCD_v1.2:Reference_Library/Devices/Dev_8/MAIN}   no device specific commands  -
        @{"Parallel" link ADCD_v1.2:Reference_Library/Devices/Dev_9/MAIN}   @{"PDCMD" link ADCD_v1.2:Inc&AD2.1/includes/devices/parrallel.h/MAIN 92}                        PDCMD_QUERY
        @{"Printer" link ADCD_v1.2:Reference_Library/Devices/Dev_10/MAIN}    @{"PRD" link ADCD_v1.2:Inc&AD2.1/includes/devices/printer.h/MAIN 30}                          PRD_PRTCOMMAND
        @{"SCSI" link ADCD_v1.2:Reference_Library/Devices/Dev_11/MAIN}       @{"HD" link ADCD_v1.2:Inc&AD2.1/includes/devices/scsidisk.h/MAIN 67}                           HD_SCSICMD
        @{"Serial" link ADCD_v1.2:Reference_Library/Devices/Dev_12/MAIN}     @{"SDCMD" link ADCD_v1.2:Inc&AD2.1/includes/devices/serial.h/MAIN 94}                        SDCMD_BREAK
        @{"Timer" link ADCD_v1.2:Reference_Library/Devices/Dev_13/MAIN}      @{"TR" link ADCD_v1.2:Inc&AD2.1/includes/devices/timer.h/MAIN 41}                           TR_ADDREQUEST
        @{"Trackdisk" link ADCD_v1.2:Reference_Library/Devices/Dev_14/MAIN}  @{"TD" link ADCD_v1.2:Inc&AD2.1/includes/devices/trackdisk.h/MAIN 73} and @{"ETD" link ADCD_v1.2:Inc&AD2.1/includes/devices/trackdisk.h/MAIN 97}                   TD_MOTOR/ETD_MOTOR


Each device maintains its own I/O request queue.  When a device receives
an I/O request, it either processes the request immediately or puts it in
the queue because one is already being processed. After an I/O request is
completely processed, the device checks its queue and if it finds another
I/O request, begins to process that request.

@{" Synchronous Vs. Asynchronous Requests " link 19-3-1}  @{" I/O Request Completion " link 19-3-2}

@ENDNODE

@NODE 19-3-1 "19 / Using A Device / Synchronous Vs. Asynchronous Requests"
As stated above, you can send I/O requests to a device synchronously or
asynchronously.  The choice of which to use is largely a function of your
application.

Synchronous requests use the @{"DoIO()" link 19-3 8} function.  DoIO() will not return
control to your application until the I/O request has been satisfied by
the device.  The advantage of this is that you don't have to monitor the
message port for the device reply because DoIO() takes care of all the
message handling.  The disadvantage is that your application will be tied
up while the I/O request is being processed, and should the request not
complete for some reason, DoIO() will not return and your application will
hang.

Asynchronous requests use the @{"SendIO()" link 19-3 13} and @{"BeginIO()" link 19-3 21} functions.  Both
return to your application almost immediately after you call them.  This
allows you to do other operations, including sending more I/O requests to
the device.  Note that any additional I/O requests you send must use
separate I/O request structures. Outstanding I/O requests are not
available for re-use until the device is finished with them.

    Do Not Touch!
    -------------
    When you use @{"SendIO()" link 19-3 13} or @{"BeginIO()" link 19-3 21}, the I/O request you pass to
    the device and any associated data buffers should be considered
    read-only.  Once you send it to the device, you must not modify it
    in any way until you receive the reply message from the device or
    abort the request.

Sending multiple asynchronous I/O requests to a device can be tricky
because devices require them to be unique and initialized.  This means you
can't use an I/O request that's still in the queue, but you need the
fields which were initialized in it when you opened the device.  The
solution is to copy the initialized I/O request to another I/O request(s)
before sending anything to the device.

Regardless of what you do while you are waiting for an asynchronous I/O
request to return, you need to have some mechanism for knowing when the
request has been done.  There are two basic methods for doing this.

The first involves putting your application into a wait state until the
device returns the I/O request to the message port of your application.
You can use the @{"WaitIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/WaitIO()}, @{"Wait()" link ADCD_v1.2:Reference_Library/Libraries/Lib_22/22-1-2} or @{"WaitPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-2-2 9} function to wait for the
return of the I/O request. It is important to note that all of the above
functions and also @{"DoIO()" link 19-3 8} may Wait() on the message reply port's
@{"mp_SigBit" link ADCD_v1.2:Inc&AD2.1/includes/exec/ports.h/MAIN 29}.  For this reason, the task that created the port must be the
same task the waits for completion of the I/O.  There are three ways to
wait:

      * @{"WaitIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/WaitIO()} not only waits for the return of the I/O request, it
        also takes care of all the message handling functions.  This is
        very convenient, but you can pay for this convenience: your
        application will hang if the I/O request does not return.

      * @{"Wait()" link ADCD_v1.2:Reference_Library/Libraries/Lib_22/22-1-2} waits for a signal to be sent to the message port.  It
        will awaken your task when the signal arrives, but you are
        responsible for all of the message handling.

      * @{"WaitPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-2-2 9} waits for the message port to be non-empty.  It
        returns a pointer to the message in the port, but you are
        responsible for all of the message handling.

The second method to detect when the request is complete involves using
the @{"CheckIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/CheckIO()} function.  CheckIO() takes an I/O request as its argument
and returns an indication of whether or not it has been completed.  When
CheckIO() returns the completed indication, you will still have to remove
the I/O request from the message port.

@ENDNODE

@NODE 19-3-2 "19 / Using A Device / I/O Request Completion"
A device will set the @{"io_Error" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} field of the I/O request to indicate the
success or failure of an operation.  The indication will be either zero
for success or a non-zero error code for failure. There are two types of
error codes:  Exec I/O and device specific. Exec I/O errors are defined in
the include file <exec/@{"errors.h" link ADCD_v1.2:Inc&AD2.1/includes/exec/errors.h/MAIN 13}>; device specific errors are defined in
the include file for each device.  You should always check that the
operation you requested was successful.

The exact method for checking @{"io_Error" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} can depend on whether you use
@{"DoIO()" link 19-3 8} or @{"SendIO()" link 19-3 13}.  In both cases, io_Error will be set when the I/O
request is returned, but in the case of DoIO(), the DoIO() function itself
returns the same value as io_Error.  This gives you the option of checking
the function return value:

    SerialIO->IOSer.io_Length   = sizeof(ReadBuffer);
    SerialIO->IOSer.io_Data     = ReadBuffer;
    SerialIO->IOSer.io_Command  = CMD_READ;
    if (DoIO((struct IORequest *)SerialIO)
        printf("Read failed.  Error: %ld\n",SerialIO->IOSer.io_Error);

Or you can check io_Error directly:

    SerialIO->IOSer.io_Length   = sizeof(ReadBuffer);
    SerialIO->IOSer.io_Data     = ReadBuffer;
    SerialIO->IOSer.io_Command  = CMD_READ;
    DoIO((struct IORequest *)SerialIO);
    if (SerialIO->IOSer.io_Error)
        printf("Read failed.  Error: %ld\n",SerialIO->IOSer.io_Error);

Keep in mind that checking @{"io_Error" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} is the only way that I/O requests sent
by @{"SendIO()" link 19-3 13} can be checked.  Testing for a failed I/O request is a minimum
step, what you do beyond that depends on your application.  In some
instances, you may decide to resend the I/O request and in others, you may
decide to stop your application. One thing you'll almost always want to do
is to inform the user that an error has occurred.

    Exiting The Correct Way.
    ------------------------
    If you decide that you must prematurely end your application, you
    should deallocate, release, give back and let go of everything you
    took to run the application.  In other words, you should exit
    gracefully.

@{" Closing the Device " link 19-3-2-1}  @{" Ending Device Access " link 19-3-2-2}

@ENDNODE

@NODE 19-3-2-1 "19 / / I/O Request Completion / Closing the Device"
You end device access by reversing the steps you did to access it. This
means you close the device, deallocate the I/O request memory and delete
the message port.  In that order!

Closing a device is how you tell Exec that you are finished using a device
and any associated resources.  This can result in housecleaning being
performed by the device.  However, before you close a device, you might
have to do some housecleaning of your own.

A device is closed by calling the @{"CloseDevice()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/CloseDevice()} function.  The
CloseDevice() function does not return a value.  It has this format:

    CloseDevice(IORequest);

where @{"IORequest" link 19-2-2} is the I/O request used to open the device.

You should not close a device while there are outstanding I/O requests,
otherwise you can cause major and minor problems.  Let's begin with the
minor problem: memory.  If an I/O request is outstanding at the time you
close a device, you won't be able to reclaim the memory you allocated for
it.

The major problem: the device will try to respond to the I/O request. If
the device tries to respond to an I/O request, and you've deleted the
message port (which is covered below), you will probably crash the system.

One solution would be to wait until all I/O requests you sent to the
device return.  This is not always practical if you've sent a few requests
and the user wants to exit the application immediately.

In that case, the only solution is to abort and remove any outstanding I/O
requests.  You do this with the functions @{"AbortIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/AbortIO()} and @{"WaitIO()" link 19-3-1 48}.  They
must be used together for cleaning up.  AbortIO() will abort an I/O
request, but will not prevent a reply message from being sent to the
application requesting the abort.  WaitIO() will wait for an I/O request
to complete and remove it from the message port.  This is why they must be
used together.

    Be Careful With @{"AbortIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/AbortIO()}!
    --------------------------
    Do not AbortIO() an I/O request which has not been sent to a
    device.  If you do, you may crash the system.

@ENDNODE

@NODE 19-3-2-2 "19 / / I/O Request Completion / Ending Device Access"
After the device is closed, you must deallocate the I/O request memory.
The exact method you use depends on how you allocated the memory in the
first place.  For @{"AllocMem()" link ADCD_v1.2:Reference_Library/Libraries/Lib_20/20-1} you call @{"FreeMem()" link ADCD_v1.2:Reference_Library/Libraries/Lib_20/20-1 11}, for @{"CreateExtIO()" link ADCD_v1.2:Reference_Library/Libraries/Lib_A/A-1-1 6} you
call @{"DeleteExtIO()" link ADCD_v1.2:Reference_Library/Libraries/Lib_A/A-1-1 6}, and for @{"CreateIORequest()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/CreateIORequest()} you call @{"DeleteIORequest()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/DeleteIORequest()}.
If you allocated the I/O request memory at compile time, you naturally
have nothing to free.

Finally, you must delete the message port you created.  You delete the
message port by calling @{"DeleteMsgPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-2 26} if you used @{"CreateMsgPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-1 39}, or
@{"DeletePort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-2} if you used @{"CreatePort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-1}.

Here is the checklist for gracefully exiting:

  * Abort any outstanding I/O requests with @{"AbortIO()" link 19-3-2-1 30}.

  * Wait for the completion of any outstanding or aborted I/O
    requests with @{"WaitIO()" link 19-3-1 48}.

  * Close the device with @{"CloseDevice()" link 19-3-2-1 9}.

  * Release the I/O request memory with either @{"DeleteIORequest()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/DeleteIORequest()},
    @{"DeleteExtIO()" link ADCD_v1.2:Reference_Library/Libraries/Lib_A/A-1-1 6} or @{"FreeMem()" link ADCD_v1.2:Reference_Library/Libraries/Lib_20/20-1 11} (as appropriate).

  * Delete the message port with @{"DeleteMsgPort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-2 26} or @{"DeletePort()" link ADCD_v1.2:Reference_Library/Libraries/Lib_24/24-1-2}.

@ENDNODE

@NODE 19-4 "19 Exec Device I/O / Devices With Functions"
Some devices, in addition to their commands, provide library-style
functions which can be directly called by applications.  These functions
are documented in the device specific FD files and Autodocs of the Amiga
ROM Kernel Reference Manual: @{"Includes and Autodocs" link 2.0Includes_Autodocs/MAIN}, and in the @{"Devices" link Devices_Manual/MAIN}
volume of this manual set.

Devices with functions behave much like Amiga libraries, i.e., you set up
a base address pointer and call the functions as offsets from the pointer.
See the "@{"Exec Libraries" link ADCD_v1.2:Reference_Library/Libraries/Lib_18/18-1-4}" chapter for more information.

The procedure for accessing a device's functions is as follows:

  * Declare the device base address variable in the global data area.
    The correct name for the base address can be found in the device's FD
    file.

  * Create a message port data structure.

  * Create an I/O request data structure.

  * Call @{"OpenDevice()" link 19-2-3}, passing the I/O request.  If OpenDevice() is
    successful (returns 0), the address of the device base may be found
    in the @{"io_Device" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} field of the I/O request structure. Consult the
    include file for the structure you are using to determine the full
    name of the io_Device field.  The base address is only valid while
    the device is open.

  * Set the device base address variable to the pointer returned in the
    @{"io_Device" link ADCD_v1.2:Inc&AD2.1/includes/exec/io.h/MAIN 28} field.

We will use the timer device to illustrate the above method.  The name of
the timer device base address is listed in its FD file as TimerBase.


#include <devices/@{"timer.h" link ADCD_v1.2:Inc&AD2.1/includes/devices/timer.h/MAIN}>

struct Library *TimerBase;            /* device base address pointer */
struct MsgPort *TimerMP;              /* message port pointer        */
struct timerequest *TimerIO;          /* I/O request pointer         */

if (TimerMP=CreatePort(NULL,NULL))    /* Create the message port.    */
{
    /* Create the I/O request.  */
    if ( TimerIO = (struct timerequest *)
         CreateExtIO(TimerMP,sizeof(struct timerequest)) )
    {
        /* Open the timer device.  */
        if ( !(OpenDevice(TIMERNAME,UNIT_MICROHZ,TimerIO,0)) )
        {
            /* Set up pointer for timer functions.  */
            TimerBase = (struct Library *)TimerIO->tr_node.io_Device;

            /* Use timer device library-style functions such as /*
            /* CmpTime() ...*/

            CloseDevice(TimerIO);     /* Close the timer device. */
        }
        else
            printf("Error: Could not open %s\n",TIMERNAME);
    }
    else
        printf("Error: Could not create I/O request\n");
}
else
    printf("Error: Could not create message port\n");
}

@ENDNODE

@NODE 19-6 "19 Exec Device I/O / Function Reference"
The following chart gives a brief description of the Exec functions that
control device I/O.  See the Amiga ROM Kernel Reference Manual: Includes
and Autodocs for details about each call.


                  Table 19-4: Exec Device I/O Functions
  ______________________________________________________________________
 |                                                                      |
 |     Exec Device                                                      |
 |     I/O Function                  Description                        |
 |======================================================================|
 |  @{"CreateIORequest()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/CreateIORequest()}  Create an @{"IORequest" 19-2-2} structure (V36).             |
 |  @{"DeleteIORequest()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/DeleteIORequest()}  Delete an IORequest created by CreateIORequest() |
 |                     (V36).                                           |
 |       @{"OpenDevice()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/OpenDevice()}  Gain access to an Exec device.                   |
 |      @{"CloseDevice()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/CloseDevice()}  Close Exec device opened with OpenDevice().      |
 |             @{"DoIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/DoIO()}  Perform a device I/O command and wait for        |
 |                     completion.                                      |
 |           @{"SendIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/SendIO()}  Initiate an I/O command.  Do not wait for it to  |
 |                     complete.                                        |
 |          @{"CheckIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/CheckIO()}  Get the status of an IORequest.                  |
 |           @{"WaitIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/WaitIO()}  Wait for completion of an I/O request.           |
 |          @{"AbortIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/exec/AbortIO()}  Attempt to abort an I/O request that is in       |
 |                     progress.                                        |
 |______________________________________________________________________|


             Table 19-5: Exec Support Functions in amiga.lib
  ______________________________________________________________________
 |                                                                      |
 |        Function                   Description                        |
 |======================================================================|
 |          @{"BeginIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/amiga_lib/BeginIO()}  Initiate an asynchronous device I/O request.     |
 |      @{"CreateExtIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/amiga_lib/CreateExtIO()}  Create an @{"IORequest" link 19-2-2} data structure.              |
 |      @{"DeleteExtIO()" link ADCD_v1.2:Inc&AD2.1/autodocs/amiga_lib/DeleteExtIO()}  Free an IORequest structure allocated by         |
 |                     CreateExtIO().                                   |
 |______________________________________________________________________|

@ENDNODE
