Chapter 6. Scanner Drivers
Prev |  | Next

## 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:

- “Driver Template”

- “Header Files”

- “Data Structures”

- “Functions You Must Write”

- “Events”

- “Installation”

- “Testing”

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

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

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

Prev | Table of Contents | Next
Chapter 5. Printing Libraries |  | Chapter 7. Scanner-Specific Options