Open(), Write, Close(), and especially IOCTL(). When writing a driver or library, what parameters or operations should be considered? Consider compatibility with similar software? Strive for simplicity or for completeness? The simple approach may allow the user to rapidly get a system plotting, but prevent the user from taking advantage of various features or options of the Gypsy. This application note should also give an application developer details about writing to a "common" set of driver calls.
To incorporate a Gypsy into a new operating environment, a driver or library should be developed. A driver typically becomes part of the operating system, and may execute in either 'user' mode, or in 'system' mode, depending upon how it is written and installed, and the operating system. In general, a driver responds to requests for a device from an application, initiates the transaction with the device, and reports the status back to the application upon completion. A library is written containing the device specific code (much like the driver) and is linked with the application. The choice on which should be developed depends upon system familiarity, system SCSI support, desired throughput, compatibility with other plotter output devices, and application source code availability. For simplicity, all references herein to drivers also applies to libraries unless explicity stated otherwise.
When writing a driver, some standard input/output (I/O) operations should be supported. For the Gypsy, a plotter adapter, most support is needed for the output operations. The "common" calls should include open(), write(), ioctl(), and close(). The description for each of these common calls provides for a fairly simple driver, but easily adapted into a more complete or tailored driver.
An open() routine should verify existence of a Gypsy at a particular SCSI ID, possibly reserve it for exclusive use, and test if the unit is ready. The three SCSI commands for doing these three operations are INQUIRY, RESERVE UNIT, and TEST UNIT READY.
The first command, INQUIRY, may already have been performed by the system. It should not report any device errors, but may report any errors caused by the initiator improperly using the INQUIRY command. A check of the INQUIRY data should include the Peripheral Qualifier and Peripheral Device Type. Expect this to be a 2. This helps double-check the user's input when installing a device driver, or running application software. The Vendor and Product Identification fields may also be checked, although they are simply text strings. Note that the Product Revision Level is also a text string, and does not have a specific format.
Depending upon the application and the number of initiators on the SCSI bus, a RESERVE UNIT command may be in order. This will prevent the output from multiple applications or multiple initiators from getting scrambled when placed onto the plotter's output medium. This command should respond an error if the device has already been reserved. The driver's response could be to wait a little while and try again, or to simply report an error back to the caller (application).
The third command, TEST UNIT READY, ensures the plotter is OPERATIONAL. The Gypsy may have been in buffered mode from the previous application or from another initiator, and still be sending data to the plotter or waiting for the plotter to advance the paper, but will still respond to the TEST UNIT READY command appropriately. If the TEST UNIT READY command does not report an error, then the Gypsy is ready to begin receiving new (even if it is still working on data from a previous application or another initiator).
If a CHECK CONDITION status is returned for any command, a REQUEST SENSE command should be performed. Some reasons for the CHECK CONDITION include: this being the first command (besides the INQUIRY) after Gypsy power on, this being the first command (besides the INQUIRY) after the plotter device became OPERATIONAL, or the plotter is NOT OPERATIONAL. These first two are pending UNIT ATTENTIONs. The Gypsy will queue up a finite number of UNIT ATTENTIONs, so a finite number of CHECK CONDITIONs should occur (unless the plotter is NOT OPERATIONAL) during the open().
If any special features are desired, they may be performed here by the driver, or the driver may require an ioctl() call later. By placing special setups here, they will apply to all applications. Changes made to support one application may adversely affect other applications.
int open(char *device, char scsibus, char scsiid, char LUN);
device is the name of the special device file.
scsibus specifies which SCSI bus, if the host has several.
scsiid specifies the Gypsy SCSI id (0 through 7) if known, else -1.
LUN for the Gypsy will be 0.
driverSPP = 0; set driver defaults to match
driverPLOT = 0; the Gypsy defaults in case the
driver_remote_command = 00h; application never set the modes.
clear driver_nowait flag
if a particular Gypsy is not requested,
scan the SCSI bus using the INQUIRY command looking for a Gypsy-2000.
set Gypsy ID based on *device, scsibus, scsiid, and LUN.
rc = RESERVE UNIT
if rc,
REQUEST SENSE
return error
while (not done)
{
rc = TEST UNIT READY
if rc
REQUEST SENSE
if NOT READY to READY transition
mark plotter as available
if READY to NOT READY transition
mark plotter as unavailable
if LU not ready
return error
else
set done
}
return Gypsy ID
-1 if unsuccessful, and errno indicates the error.
Gypsy ID, if successful, for use with the other driver calls.
A write() routine should issue one or more PLOT commands to send the data to the plotter. Because the Gypsy PLOT command is limited to receiving 64KB of data, the driver may need to break up a single write() into multiple PLOT commands. In all cases, a CHECK CONDITION should cause a REQUEST SENSE command on the SCSI bus. A simple driver may then stop and return an error to the application. The error returned to the application may provide some indication of the problem based upon the information from the REQUEST SENSE command. A more complicated driver may attempt to handle certain cirumstances on its own, and return other problems back to the application for the user to take care of.
For a more sophisticated driver, a 'nowait' flag may be incorporated into the ioctl() call which requests the write() call to initiate the host's SCSI system calls to perform the PLOT command, but to not wait for the system to complete the PLOT command. The technique required to accomplish this (if available) varies from system to system. Drivers which incorporate it may be referred to as asynchronous drivers. This should not be confused with the SCSI message 'synchronous data transfer request'.
A synchronous driver is called to perform an action, waits for the action to be completed, then returns to the caller. This is probably the simplest way to start out when writing a driver.
An asynchronous driver is called to perform an action, starts the process up, returns to the caller, and then monitors the process for completion.
The SCSI message 'synchronous data transfer request' is a negotiation over the SCSI bus between the initiator and the target deciding how many bytes may be transferred during the SCSI bus data phase, and how fast they may be sent. (They negotiate a period and offset to accomodate the slowest device.)
int write(int gid, *buffer, int writesize);
gid Gypsy ID returned from the open() call.
buffer contains the data to be sent to the plotter
writesize is the number of bytes to be sent
written = 0
while (written < writesize)
{
plotsize = min(writesize-written,65536)
rc = PLOT(*buffer+written,plotsize)
if rc
REQUEST SENSE
return error
written = written + plotsize
}
return written
-1 if unsuccessful, and errno indicates the error.
number of bytes written if successful.
An ioctl() routine provides access to all other functions of the Gypsy, and all other information relating to the SCSI bus. It is the generic interface for performing any other SCSI command needed by the application. This should include the FORMAT command as a minimum, but may also include one or more of the following SCSI commands: RESERVE UNIT, RELEASE UNIT, MODE SENSE, MODE SELECT, REQUEST SENSE, TEST UNIT READY, STOP PRINT, SEND DIAGNOSTIC, and RECEIVE DIAGNOSTIC RESULTS.
A standard ioctl() requires a device handle of some sort, a function to be performed, and two additional arguments, the first of which is a pointer to a buffer area.
A sample of the functions to be implemented follows.
func *arg1 arg2 Description LPNOWAIT n/a n/a signal driver to not wait for CMD CMPLT LPGETREGS databuffer n/a issue a REQUEST SENSE command LPCOMMAND fmtbuffer n/a issue a FORMAT command LPMODEGET modebuffer n/a issue a MODE SENSE command LPMODESET modebuffer n/a issue a MODE SELECT command LPRESERVE n/a n/a issue a RESERVE UNIT command LPRELEASE n/a n/a issue a RELEASE UNIT command LPSTOP n/a n/a issue a STOP PRINT command
For compatiblity across platforms with all Gypsy drivers, a minimal ioctl() should include the first three functions.
int ioctl(int gid, int func, void *arg1, int arg2);
gid Gypsy ID returned from the open() call.
func
*arg1
arg2 additional argument which is not yet defined for the minimal
Gypsy driver
switch (func)
{
default:
return error
LPNOWAIT:
set driver_nowait flag
return 0
LPGETREGS:
rc = REQUEST SENSE
if rc
REQUEST SENSE
return error
else
place the REQUEST SENSE data into user's databuffer
return 0
LPCOMMAND:
switch (*fmtbuffer)
{
SET_SPP: driverSPP = 1; break;
CLR_SPP: driverSPP = 0; break;
PLOT: driverPLOT = 1; break;
PRINT: driverPLOT = 0; break;
RFFED: driver_remote_command = 10h; break;
RLTER: driver_remote_command = 08h; break;
REOTR: driver_remote_command = 04h; break;
CLEAR: driver_remote_command = 02h; break;
RESET: driver_remote_command = 01h; break;
}
rc = FORMAT(80h + driverPLOT<<6 + driverSPP<<5, driver_remote_command);
driver_remote_command = 00h;
if rc
REQUEST SENSE
return error
else
return 0
}
-1 if unsuccessful, and errno indicates the error.
0 if successful.
A close() routine should issue RELEASE UNIT if nothing else. If buffered mode of operation was enabled, the driver may wish to verify the Gypsy is finished handshaking all data with the plotter by returning to unbuffered mode via MODE SELECT, and issuing a zero length PLOT command. When this sequence finishes on the SCSI bus, the initiator may be assured all Gypsy buffered data has been sent to the plotter.
The close() also gives the driver a chance to clean up any 'loose ends' it may have generated along the way. Variables may be reset and allocated memory may be freed.
int close(int gid);
gid Gypsy ID returned from the open() call.
rc = RELEASE UNIT
if rc
REQUEST SENSE
clear gid
return error
else
clear gid
return 0
-1 if unsuccessful, and errno indicates the error.
0 if successful.