Chapter 6. Scanner Drivers

This chapter discusses scanner driver development. It provides a detailed analysis of the template scanner driver.

The following major topics are discussed in this chapter:

The information presented in this chapter should be enough to write a scanner driver. However, if you wish to know more, Appendix E, “Scanner Driver Architecture,” is an in-depth discussion of the architecture of a scanner driver.

Driver Template

The source code files for the template scanner driver are in the directory /usr/impressario/src/scan/template_driver. This template has code to handle all the interprocess communication necessary for well-behaved scanner drivers (see Chapter 8, “Generic Scanner Interface”).

To develop a new scanner driver, start by copying the template files to the directory where you will be developing the driver. The only files that you need to modify are scan.c and Makefile; Do not modify any of the other files unless you are familiar with the information in Appendix E.

This document refers to the main.c module, which implements the interprocess communication part of a scanner driver and should not be modified; and scan.c, which you should modify to support the scanner for which you are writing a driver.

Header Files

There are four header files in /usr/include that are useful to scanner driver developers:

scanner.h 

Defines the interface used by application programmers to communicate with scanner drivers. Contains typedef and #define definitions needed to communicate with the application.

scandrv.h 

Contains the dispatch loop interface, some error messages, and the queue utility routines.

scanipc.h 

Contains #define definitions for command numbers and the types of arguments and results.

scanconv.h 

Contains prototypes for functions that convert between data types and functions that do replicative zooming on rows of image data.

Data Structures

The following data structures are used to communicate between scanner driver template modules. Understanding each field is key to understanding what your part of the driver (the code in scan.c) must do. These data structures are defined in /usr/impressario/src/scan/template_driver/scan.h.

SCANINFO Data Structure

The SCANINFO data structure is used to store static information about a scanner. The scan.c module uses SCANINFO to communicate with the main.c module. The SCANINFO data structure is defined as follows:

typedef struct tag_scaninfo {
   int metric;                                /* metric for res, page size*/
   float pagex, pagey, pagewidth, pageheight; /* page size */
   float minxres, maxxres, minyres, maxyres;  /* resolution bounds */
   float *xres, *yres;                        /* resolution arrays */
   int nres;                                  /* size of arrays */
   int canZoom;                               /* 1 if scanner can zoom */
   SCDATATYPE *types;                         /* supported types */
   int ntypes;                                /* number of types */
   SCANFUNC *options;                         /* scanner-specific options */
   int noptions;                              /* number of options */
   void *priv;                                /* private member */
   SCFEEDERFLAGS feederFlags;                 /* feeder flags */
} SCANINFO;

Field definitions:

metric 

Set metric to SC_INCHES or SC_CENTIM, depending on whether it is more convenient to have measurements and resolutions expressed in terms of inches or centimeters.

pagex, pagey, pagewidth, pageheight 


pagex and pagey are the coordinates of the upper left corner of the scannable area (almost always 0 and 0). pagewidth and pageheight are the width and height of the scannable area, respectively. All four fields are expressed in the units defined by the metric field.

minxres, maxxres, minyres, maxyres 


minxres is the smallest supported horizontal resolution, maxxres is the largest supported horizontal resolution, minyres is the smallest supported vertical resolution, and maxyres is the largest supported vertical resolution. These are all expressed in pixels per the unit expressed by the metric field.

xres, yres, nres 

For scanners that support discrete resolutions (as opposed to scanners that support all resolutions with equal quality, within the bounds given above), xres and yres are arrays of the supported resolutions in the horizontal and vertical directions. nres is the number of elements in each of these arrays.

For scanners that support arbitrary resolutions (that is, scanners that do their own scaling), nres is 0. The main.c module takes nres equal to 0 to signify that it doesn't need to do any scaling of scan data to satisfy preview requests from the scanning application.

canZoom 

This parameter specifies whether or not the scanner can support resolutions other than those specified in the xres and yres arrays when nres is nonzero. In this case, the resolutions in the xres and yres arrays represent preferred resolutions that results in superior image quality.

If nres is 0, the main.c module assumes that the scanner itself can do zooming, regardless of the canZoom flag.

types, ntypes 

types is an array of SCDATATYPE structures (see Chapter 8, “Generic Scanner Interface”), and ntypes is the number of types supported by the scanner.

options, noptions 


This array of functions implements scanner-specific options for this scanner (see Chapter 7, “Scanner-Specific Options”), and noptions is the number of such options.

priv 

This parameter is used by the scan.c module (the one you write) to store a pointer to whatever state information is necessary to identify a particular scanner once it's been opened. This is provided so that you can avoid the use of global variables in the scan.c module.

feederFlags 

These flags indicate the presence of an automatic document feeder. The SC_HASFEEDER bit (see /usr/include/scanner.h) of this flag should be set if a feeder is attached to the scanner being supported. The SC_AUTOFEED flag should be set if each call to DoScan() automatically results in the next sheet of paper being fed. If the scanner can feed on demand, the SC_PROGFEED bit should be set. It is not an error to have the SC_AUTOFEED flag and the SC_PROGFEED flag both set.

If the scanner being supported does not have a document feeder, this member can be safely ignored and the main.c module will not try to call any of the document feeder functions (see below).

SCANPARAMS Data Structure

The SCANPARAMS data structure contains dynamic values used to specify the parameters of a scanning operation, and also some administrative details. The SCANPARAMS data structure is defined as follows:

typedef struct tag_scanparams {
    float xres, yres;
    float x, y, width, height;
    SCDATATYPE type;
    int preview;
    SCQUEUE *scanq, *sfreeq;
    int xpixels, ylines, xbytes;
    void (*convert)(void *from, int fromx, void *to, int tox, int *zmap);
    int maxmem; /* maximum amount of memory to allocate */
    int readlines;
    SCANINFO *s;
} SCANPARAMS;

The fields of the SCANPARAMS data structure are defined as follows:

xres, yres 

The scanning resolution to be used for a particular scan. The main.c module always ensures that these resolutions are among those advertised in the xres and yres fields of the SCANINFO struct, unless the canZoom field of the SCANINFO struct is nonzero or the nres field is 0. In any case, xres and yres are always within the resolution bounds specified in the SCANINFO struct.

xres and yres are expressed in dots per the unit specified in the metric field of the SCANINFO struct.

x, y, width, height 


The horizontal (x) and vertical (y) coordinates of the upper left corner of the window to be scanned and its width and height. The main.c module ensures that this image falls within the bounds of the pagex, pagey, pagewidth, and pageheight fields of the SCANINFO struct.

x, y, width, and height are expressed in the units specified in the metric field of the SCANINFO struct.

type 

The type of scan data expected. The main.c module ensures that it is one of the types specified in the types field of the SCANINFO struct.

preview 

This field is set to 1 if this is a preview scan, and 0 otherwise.

scanq, sfreeq 

sfreeq is a queue whose elements are free buffers to put scanned data into, and scanq is a queue whose elements are buffers that have scanned data in them. DoScan(), which you write (see below), removes buffers from sfreeq, scans the data into them, then adds them to scanq. The main.c module is responsible for taking buffers from scanq, disposing of the data appropriately, and putting them back on sfreeq.

xpixels, ylines, xbytes 


The number of pixels in a scan line, the number of scan lines in the scan, and the number of bytes in a scan line. The scan.c module is responsible for calculating these values in SetupScan(), which you write (see below).

void (*convert)(void *from, int fromx, void *to, int tox, int *zmap) 


This function converts data from a type that the scanner supports to the requested data type. If the scanner directly supports all the data types that are being advertised to the scanning application (the types field of the SCANINFO struct), the scan.c module can ignore this field.

For example, this function can be used for color scanners that return the red, green, and blue components of each scan line separately; that is, a line of five pixels would have the following layout:

RRRRRGGGGGBBBBB

This needs to be converted to chunky data format, as shown below:

RGBRGBRGBRGBRGB

To do this, simply set the convert field to SCBandRGB8ToPixelRGB8 in SetupScan() (see below). The following functions are available in libscan for converting:

  • SCBandRGB8ToPixelRGB8()

  • SCGrey8ToMono()

  • SCBandRGB8ToMono()

maxmem 

The maximum amount of memory that should be allocated for storing scan data. This field is to be taken into account in the calculation of readlines in SetupScan() (see below).

readlines 

The number of lines to read at a time. readlines is the maxmem field divided by the xbytes field if scanning is benefited by scanning in large chunks. If there is no benefit, the number is 1.

The problem with maxmem/xbytes is that when maxmem is large, interactive feedback to the user of the scanning application is limited. Ideally, the scanner buffers data internally, so you can scan perhaps an inch at a time without the scan head pausing. That way, the scanning application can consume the scan data while the scan head gets the rest of the data.

s 

A pointer to the SCANINFO struct that OpenScanner() returned (see below).

Functions You Must Write

After copying the template to your build area, you must edit the file scan.c and implement the functions listed in Table 6-1.

These functions are described in detail in the following sections.

Table 6-1. Functions To Be Written by the Driver Developer

Function Name

Description

OpenScanner()

Opens the scanner

SetupScan()

Called before a scanning operation

DoScan()

Gets data from the scanner

SetFeederFlags()

Called when the scanner application calls SCFeederSetFlags

AdvanceFeeder()

Advances feeder to next document

FeederReady()

Tests whether the feeder is ready to feed another document

PrintID()

Prints a string describing the type of scanning supported

FindScanners()

Prints device for supported scanners

InstallScanner()

Installs a new scanner

DeleteScanner()

Deletes a scanner


OpenScanner() Function

This function is called when the driver is first invoked.

For example:

SCANINFO *
OpenScanner(char *dev)
{
    static SCANINFO scan;
    
    /*
    Your code here!
    */

    if (something goes wrong) {
        drverr = appropriate error code;
        return NULL;
    }
    */
    
    return &scan;
}

dev is the name of the device (usually a device special file in /dev/scsi for SCSI devices) to open in order to communicate with the scanner. The task of the OpenScanner() function is to “open” dev, make sure that it corresponds to a device that scan.c knows how to talk to, get it into some reasonable initial state, and fill in a SCANINFO structure for the scanner. If all goes well, a pointer to the SCANINFO structure is returned.

If anything goes wrong, OpenScanner() should set the global variable drverr and return NULL. The value for drverr should be chosen from those in /usr/include/sys/errno.h or /usr/include/scanner.h; that value is communicated back to the scanning application, which can use the SCPerror() or SCErrorString() functions in libscan.a to get a human-readable error message that explains why OpenScanner() failed.


Caution: If you are writing a driver for a SCSI scanner, and you are using dslib(3X), make sure that you pass the O_EXCL flag defined in /usr/include/fcntl.h to dsopen:


dsreq_t *dsp = dsopen(dev, O_RDONLY | O_EXCL);

If you pass the O_EXCL flag, the open will fail with errno set to EBUSY if dev is the /dev/scsi device of a mounted disk; otherwise, the open can succeed and you could really screw up the disk!

In addition, it is recommended that before issuing any other SCSI commands you perform an inquiry command, and verify that the device is a scanner by examining the Device Type code of the inquiry buffer. (This field should be set to 6. You can use the INV_SCANNER #define from /usr/include/invent.h.) It is also recommended that you examine the vendor and product identifiers to make sure the device is a scanner of the type for which this driver is being written.

SetupScan() Function

This function is called with a pointer to a SCANPARAMS struct to prepare for a scanning operation.

For example:

int
SetupScan(SCANPARAMS *params)
{
    /*
    Your code to tell the scanner the resolution, scanning window, 
    and data type.
    */
    
    /*
    Your code to find out from the scanner how many pixels are in a scan
    line, how many scan lines are in the scan, and how many bytes are
    in a scan line.
    */
    
    /*
    Your code to figure out what readlines should be, taking into
    consideration maxmem and xbytes.
    */
    
    if (anything went wrong) {
        drverr = an appropriate error code;
        return -1; /* indicates failure */
    }
    
    return 0; /* indicates success */
}

SetupScan() performs the following operations:

  1. SetupScan() does whatever is necessary to anticipate the scan defined by the fields xres, yres, x, y, width, height, and type.

  2. If type is not supported directly by the scanning device, then the convert field should be set to a function that converts data returned from the scanner to the appropriate type.

  3. SetupScan() queries the scanning device or does some calculations to determine the number of pixels in a scan line, the number of scan lines in the scan, and the number of bytes in a scan line. The xpixels, ylines, and xbytes fields of params are set appropriately.

  4. SetupScan() sets the readlines field of params to the number of lines that it expects to scan at a time, taking maxmem and xbytes into account.

  5. If at any point something goes wrong, set drverr to a value from /usr/include/sys/errno.h or /usr/include/scanner.h and return -1 to indicate a failure. If all goes well, return 0 to indicate success.

DoScan() Function

This function retrieves the data from the scanner.

For example:

void
DoScan(SCANPARAMS *params)
{
    SCANINFO *s = params->s;
    void *buf;
    int row, toread, curline;
   
    prctl(PR_TERMCHILD);
   
    for (curline = 0; curline < params->ylines;
        curline += params->readlines) {
        toread  = MIN(params->readlines, 
                  params->ylines - curline);
   
        buf = SCDequeue(params->sfreeq);
        /*
         * Get the scan data here!
         */
   
        /*
         * Chop the buffer up into scan line sized chunks
         */
        while (toread--) {
            SCEnqueue(params->scanq, buf);
            buf = (char *)buf + params->xbytes;
        }
    }
   
    exit(0);
}

DoScan() executes as its own process, sharing its address space with its parent, which is the process that communicates with the scanning application. (See the sproc(2) reference page. DoScan() is the entry parameter to sproc.)

Before entering the while loop, do whatever else is necessary to initialize the scanner if there's unfinished business from SetupScan(). Note the use of params->readlines, which you set in SetupScan(). In the body of the loop, the following things happen:

  1. DoScan() computes how many scan lines to read this time through the loop. This is either readlines, which was set in SetupScan(), or the number of lines remaining to scan:

    toread = MIN(params->readlines,
             params->ylines - curline);
    

  2. DoScan() gets a buffer from the free queue into which the data is scanned:

    buf = SCDequeue(params->sfreeq);
    

  3. DoScan() transfers toread lines of data from the scanning device to buf. This is the interesting part, that you have to write specifically for your scanner.

  4. DoScan() puts the lines just scanned onto the scan queue. This involves chopping up the buffer into chunks the size of a scan line. Don't worry, main.c knows how to put the buffers back together before putting them back on the free queue!

    while (toread--) {
       SCEnqueue(params->scanq, buf);
       buf = (char *)buf + params->xbytes;
    }
    

Since DoScan() is its own process, it calls the exit function instead of returning when it finishes scanning. If everything goes OK, DoScan() calls exit with a status of 0. If anything goes wrong, DoScan() sets the global variable drverr to an appropriate value from sys/errno.h or scanner.h and calls exit with a status of 1. (See the exit(2) reference page.)

SetFeederFlags() Function

The SetFeederFlags() function is called when the scanner application calls SCFeederSetFlags to specify whether automatic (SC_AUTOFEED) or programmatic (SC_PROGFEED) feeding is desired. This only happens if the feederFlags member of the SCANINFO struct returned by OpenScanner() has all three of the SC_HASFEEDER, SC_AUTOFEED, and SC_PROGFEED bits set.

For example:

int
SetFeederFlags(SCANINFO *scan, SCFEEDERFLAGS flags)
{
    drverr = SCENOFEEDER;
    return -1;
}

The template version of this function sets drverr to indicate that no feeder is present; if a feeder is present, SetFeederFlags() must set a flag so that it knows whether to automatically feed the next document in the next call to DoScan().

AdvanceFeeder() Function

The AdvanceFeeder() function is called only if the SC_PROGFEED bit is set in the feederFlags member of the SCANINFO struct returned by OpenScanner(). This function should advance the feeder to the next document. If the feeder is empty or jammed, return -1 and set drverr to an appropriate error code from /usr/include/scanner.h or /usr/include/sys/errno.h.

For example:

int
AdvanceFeeder(SCANINFO *scan)
{
    drverr = SCENOFEEDER;
    return -1;
}

FeederReady() Function

This function is called only if the SC_HASFEEDER bit of the feederFlags field of the SCANINFO struct returned by OpenScanner() is set.

For example:

int
FeederReady(SCANINFO *scan)
{
    drverr = SCENOFEEDER;
    return -1;
}

FeederReady() should return 0 if there is a document in the feeder; that is, if the next call to AdvanceFeeder() should succeed. If the feeder is empty, FeederReady() should return -1 and set drverr to SCFEEDEREMPTY.

PrintID() Function

The PrintID() function is used by the -query option that all scanner drivers support. It should print a string that identifies the type of scanner supported by this scanner driver, and one or more interface types supported. The scanner install tool, scanners, uses this information to help the end user choose the driver that best suits a particular scanner.

For example:

void
PrintID(FILE *fp)
{
    fprintf(fp, "Your Scanner Name\n");   /* String describing scanner */
    fprintf(fp, "SCSI Serial Parallel\n"); /* Device type; can be list */
}

FindScanners() Function

The FindScanners() function is also used to implement the -query option.

For example:

void
FindScanners(FILE *fp)
{
    inventory_t *inv;
    char device[100];
    dsreq_t *dsp;
    /* int because it must be word aligned. */
    int inqbuf[(sizeof(INQDATA) + 3)/sizeof(int)];
    INQDATA *inq = (INQDATA *)inqbuf;
    
    /*
     * This example looks for SCSI scanners; do whatever is necessary
     * to find other types of scanner here.
     */
    setinvent();
    while ((inv = getinvent()) != NULL) {
        if (inv->inv_class == INV_SCSI && inv->inv_type == INV_SCANNER) {
            sprintf(device, "/dev/scsi/sc%dd%dl0", inv->inv_controller,
               inv->inv_unit);
            if ((dsp = dsopen(device, O_RDONLY)) == NULL) {
                continue;
            }
    
            if (inquiry12(dsp, (char *)inq, sizeof *inq, 0) == 0
               && strncmp((char *)inq->vid, "Your vendor", 11) == 0 &&
               strncmp((char *)inq->pid, "Your product", 12) == 0) {
                  fprintf(fp, "SCSI %s\n", device);
            }
            dsclose(dsp);
        }
    }
    
    endinvent();
}

FindScanners() should search the system for scanners that this driver is capable of supporting, and for each such scanner it prints the type of device (SCSI, Serial, Parallel, GPIB, EISA, or Other), a space, and the pathname that should be passed to OpenScanner() in order to access that scanner.

This gives scanners more information that it can use to help the end user pick a driver for a particular scanner. It is by no means required that FindScanners() find all scanners that it is capable of supporting; it is OK to do nothing at all here, especially if there is no hope of finding a scanner you support in a reasonable amount of time. This is important; scanners invokes EVERY scanner driver installed with the -query option when the user adds a scanner, so this function should be fast!

InstallScanner() Function

This function is called when the scanner driver is invoked with the -install option. InstallScanner() is used by scanners when the user tries to install a new scanner.

For example:

int
InstallScanner(char *dev)
{
   printf("The template driver doesn't support %s\n", dev);
   return -1;
}

The purpose of this entry point is verify that dev corresponds to a scanning device that this driver knows how to support, and to do any scanner-specific installation that is necessary.

The following example implementation calls OpenScanner() to verify that dev corresponds to a valid scanning device, and then changes the permissions of dev so that users other than root can access the scanner. This is important, because scanner drivers should not normally be set to user ID root programs, and users other than root want to use scanners. When InstallScanner() is called by scanners, the driver will have root permissions, which enables it to call chmod(2) on dev or create any auxiliary files or other resources that it needs:

int 
InstallScanner(char *dev)
{
    SCANINFO *scan;
    
    scan = OpenScanner(dev);
    
    if (!scan) {
        printf("Can't access %s: %s\n", dev, 
                SCErrorString(drverr));
        return -1;
    }
    
    chmod(dev, 0666);
    return 0;
}

If an error occurs, InstallScanner() should print an error message to standard out and return -1. The main.c module exits with a nonzero exit status if InstallScanner() returns -1, and scanners reads the driver's standard output and displays it to the user if the main.c exits with a nonzero status. That way, the exact cause of the error is propagated to the user.

DeleteScanner() Function

The DeleteScanner() function is called when the driver is invoked with the -delete option by scanners. This gives the driver the opportunity to do any scanner-specific deletion required. This can be useful if auxiliary files specific to this scanner were created in InstallScanner().

For example:

int 
DeleteScanner(char *dev)
{
   return 0;
}

If an error occurs, DeleteScanner() prints an error message to standard out and returns -1. In this case, scanners displays this error message to the user and refuses to delete the scanner, so in most cases DeleteScanner() should return 0.

Events

Impressario scanner drivers can send events to scanner applications. Currently, the only type of event supported is an event to notify the scanner application that the resolutions, page size, data types, or feeder flags supported by the scanner driver have changed.

This typically happens when the user selects a new input media option from the scanner specific options program (see Chapter 8). For example, some scanners have transparency units, and when scanning transparencies the scanning page size is different than when scanning normal paper. So when the user selects the option to scan a transparency, the scanner driver needs to inform the scanning application that it must query to find out the new page size.

Events are sent to the scanner application by filling in an SCEVENT structure and calling SCDriverSendEvent(). The SCEVENT structure is defined as follows:

typedef struct tag_infoChange {
    unsigned int pageSizeChanged : 1;
    unsigned int resolutionChanged : 1;
    unsigned int dataTypesChanged : 1;
    unsigned int feederFlagsChanged : 1;
} SCINFOCHANGE;

#define SCEVENT_INFOCHANGE 1

typedef struct tag_scevent {
    unsigned int eventType;
    union {
        SCINFOCHANGE infoChange;
    event;
} SCEVENT;

The SCDriverSendEvent() function has the following prototype:

int SCDriverSendEvent(SCEVENT *event)

To inform the scanner application that it needs to query the new page size, the driver executes the following code:

SCEVENT event;

event.eventType = SCEVENT_INFOCHANGE;
event.event.infoChange.pageSizeChanged = 1;
event.event.infoChange.resolutionChanged = 0;
event.event.infoChange.dataTypesChanged = 0;
event.event.infoChange.feederFlagsChanged = 0;
if (SCDriverSendEvent(&event) == -1) {
    handle error;
}

Installation

After the driver is built, make sure that the -query option works, then copy it to /usr/lib/scan/drv and run scanners, the scanner install tool. See Figure 6-1.

Figure 6-1. Scanner Install Tool

Figure 6-1 Scanner Install Tool

When the scanners panel comes up, use the “Install...” item on the Scanner menu to bring up the “Install New Scanner” dialog box. If you implemented PrintID() correctly, you see your scanner driver's ID string in the list. If you implemented FindScanners(), clicking on your scanner driver type should fill in the “Device” field. Otherwise, type in the device that corresponds to your scanner.

Testing

Give the scanner a name and click OK, then run gscan. See Figure 6-2.

Figure 6-2. gscan Panel

Figure 6-2 gscan Panel

You can either provide your scanner's name on the command line or use the Setup menu to choose your scanner as the scanning source.

Severe scanner driver malfunctions can cause gscan to hang. If this happens, open a shell and enter:

/etc/killall gscan