Chapter 11. Driver Example

This chapter displays the code of a complete device driver. The driver has no hardware dependencies, so it can be used for experimentation in any IRIX 6.5 system.


Note: This driver is not intended to have a practical use, and it should not be installed in a production system.

The example driver has the following purposes:

Installing the Example Driver

Use the following steps to install and test the example driver. Each step is expanded in the following topics.

  1. Obtain the source code files.

  2. Compile the source to obtain an object file.

  3. Set up the appropriate configuration files.

  4. Reboot the system and verify driver operation using the supplied unit-test program.

Obtaining the Source Files

The example driver consists of the following five source files:

snoop.h 

Header file that declares ioctl command codes, data structures, and macros used in the driver.

snoop.c 

Driver source module.

snoop.master 

Descriptive file for /var/sysgen/master.d.

snoop.sm 

A USE statement for /var/sysgen/system.

usnoop.c 

User-level program to exercise the driver.

These files, and other example code in this book, are available from the SGI TechPubs server, http://techpubs.sgi.com/ (it requires patience to recreate the files by copying and pasting from the online manual).

Compiling the Example Driver

Compile using the techniques described under “Compiling and Linking” in Chapter 9.

When the driver is compiled with the -DDEBUG option, all its informational displays are enabled. Without that option, it only displays messages related to unexpected error returns from kernel functions.

Configuring the Example Driver

Before you configure the example driver into the kernel, you should set the system with a debugging kernel, as described under “Preparing the System for Debugging” in Chapter 10.

Configure the example driver to IRIX by copying files as follows:

  • Copy the object file, snoop.o, to /var/sysgen/boot.

  • Edit the descriptive file, snoop.master, and make any desired changes—for example, making the driver nonloadable.

  • Copy the edited descriptive file to /var/sysgen/master.d/snoop (do not use the .master suffix on the filename).

  • Review the snoop.sm file. It must contain the statement USE snoop. You can also insert a DRIVER_ADMIN statement, as described under “Creating Device Special Files”.

  • Copy the snoop.sm file to /var/sysgen/system.

  • Run the autoconfig program to build a new kernel. Run setsym to install symbols in the kernel. Reboot the system.

If you compiled the example driver with -DDEBUG, it displays several informational lines to the system console from its pfxinit(), pfxstart(), and pfxreg() entry points, as shown in Example 11-1.

Example 11-1. Startup Messages from snoop Driver

snoop_: created /snoop
snoop_: added device edge, base 0xa80000002044d800
snoop_: added device attr, base 0xa80000002044d980
snoop_: added device hinv, base 0xa80000002044db00
snoop_: start() entry point called
snoop_: reg() entry point called

To disable the driver later, change USE to EXCLUDE, run autoconfig, and reboot.

Creating Device Special Files

The driver creates three vertexes in the hwgraph. By default they are named /hw/snoop/edge, /hw/snoop/attr, and /hw/snoop/hinv. The three device names “edge,” “attr,” and “hinv” are fixed, but the path leading to them is under your control. To use a path other than /hw/snoop, for example /hw/dtest/snoop, you place a DRIVER_ADMIN statement in the snoop.sm file, as shown in Example 11-2.

Example 11-2. Driver Administration Statement in snoop.sm

TO BE SUPPLIED - API UNDER DESIGN


Verifying Driver Operation

You can verify operation of the driver by operating the usnoop program. Compile the usnoop.c source file. Run it with root privileges. If you have changed the path for the snoop devices as described in the preceding topic, specify the changed path as the command argument. At the prompt “path:” enter an absolute or relative path in /hw.

Example 11-3. Typical Output of snoop Driver Unit Test

# ./usnoop
enter path: /hw/rdisk 
 
Path read:
/hw/rdisk
 
Edges:
dks0d1s0
dks0d1s1
swap
root
volume_header
dks0d1vol
dks0d1vh
 
Attrs:
 
Hinv:
enter path: volume_header 
 
Path read:
/hw/scsi_ctlr/0/target/1/lun/0/disk/volume_header/char
 
Edges:
 
Attrs:
 
Hinv:
enter path: ../../.. 
 
Path read:
/hw/scsi_ctlr/0/target/1/lun/0
 
Edges:
scsi
disk
 
Attrs:
 
Hinv:
enter path:

When the snoop driver is compiled with -DDEBUG, numerous debugging messages appear on the console terminal at the same time. If you run usnoop from the console terminal, the debugging messages are interspersed with usnoop output.

Example Driver Source Files

The four source files of the example driver are displayed in the following topics:

Descriptive File

*
* IRIX 6.4 Example driver "snoop" descriptive file
* Store in /var/sysgen/master.d/snoop
*
* Flags used:
* c: character type device (only)
* d: dynamically loadable kernel module
* R: autoregister loadable driver
* n: driver is semaphored
* s: software device driver
* w: driver is prepared to perform any cache write back operation (none)
*
* External major number (SOFT) is an arbitrary choice from
* the range of numbers reserved for customer drivers.
*
* #DEV is passed in to the driver and used to configure its info array.
*
*FLAG   PREFIX  SOFT    #DEV    DEPENDENCIES
cdnswR   snoop_   77      -b
 
$$$

System File

*
* Lboot config file for IRIX 6.4 example driver "snoop"
* Store as /var/sysgen/system/snoop.sm
*
USE: snoop

Header File

/**************************************************************************
 *                                                                        *
 *               Copyright (C) 1993, Silicon Graphics, Inc.               *
 *                                                                        *
 *  These coded instructions, statements, and computer programs  contain  *
 *  unpublished  proprietary  information of Silicon Graphics, Inc., and  *
 *  are protected by Federal copyright law.  They  may  not be disclosed  *
 *  to  third  parties  or copied or duplicated in any form, in whole or  *
 *  in part, without the prior written consent of Silicon Graphics, Inc.  *
 *                                                                        *
 **************************************************************************/
#ifndef __SNOOP_H__
#define __SNOOP_H__
#ifdef __cplusplus
extern "C" {
#endif
/**************************************************************************
| The driver creates character special device nodes in the hwgraph, by
| default at /hw/snoop/{edge,attr,hinv}  However you can place a statement
| in the /var/sysgen/system/irix.sm file to establish a different path,
| for example:
|         DRIVER_ADMIN snoop_ hwpath = /hw/admin/snoopy/nodes
| The driver prefix must be given exactly, as must the name "hwpath".
| The argument must be a valid /hw path that does not exist when the
| driver initializes.  The following constant gives the attr-name used:
***************************************************************************/
#define ADMIN_LABEL "hwpath"
/**************************************************************************
| The following definitions establish the ioctl() command numbers that 
| are recognized by this driver.  See ioctl(D2) for comments.  Ascii
| uppercase letters, minus 64, fit in 5 bits, so the command #s are:
|    0b0000 0000 0sss ssnn nnno oooo #### ####
| These definitions are useful in client code as well as the driver.
***************************************************************************/
#define IOCTL_BASE ((('S'-64)<<18)|(('N'-64)<<13)|(('O'-64)<<8))
#define IOCTL_MASTER_TEST   (IOCTL_BASE + 1)
#define IOCTL_MASTER_GO     (IOCTL_BASE + 2)
#define IOCTL_CLOSING       (IOCTL_BASE + 6)
#define IOCTL_PATH_READ     (IOCTL_BASE + 9)
#define IOCTL_VERTEX_GET    (IOCTL_BASE + 15)
#define IOCTL_VERTEX_SET    (IOCTL_BASE + 16)
 
#ifdef _KERNEL /* remainder is only useful to the driver */
#include <sys/types.h>      /* all kinds of types inc. vertex_hdl_t */
#include <sys/kmem.h>       /* kmem_zalloc, kmem_free */
#include <sys/ksynch.h>     /* locks */
#include <sys/ddi.h>        /* many utility functions */
#include <sys/invent.h>     /* inventory_t */
#include <sys/hwgraph.h>    /* hwgraph functions */
#include <sys/driver.h>     /* driver_admin functions */
#include <sys/cred.h>       /* for cred_t used in open/read/write */
#include <sys/cmn_err.h>    /* for cmn_err and its constants */
#include <sys/errno.h>      /* error constants */
#include <sys/mload.h>      /* mload version string */
/**************************************************************************
| The purpose of the following macros are to make it possible to define
| the driver prefix in exactly one place (the PREFIX_NAME macro) and then
| to invoke that prefix anywhere else -
|   - as part of function names, e.g. <prefix>open(), <prefix>init().
|   - as a character literal, as in pciio_driver_register(..."prefix")
|   - automatically as part of other macros for example debug displays
***************************************************************************/
#define PREFIX_NAME(name) snoop_ ## name
/* -----  driver prefix:  ^^^^^^  defined here only */
/* utility macros, not to be used directly */
#define PREFIX_ONLY PREFIX_NAME( )
#define STRINGIZER(x) # x
#define EVALUEIZER(x) STRINGIZER(x)
#define PREFIX_STRING EVALUEIZER(PREFIX_ONLY)
/*
| Define driver entry point macros in alpha order.  This is your basic
| character driver: open-read-write-ioctl-close.
*/
#define PFX_CLOSE     PREFIX_NAME(close)
#define PFX_DEVFLAG   PREFIX_NAME(devflag)
#define PFX_INIT      PREFIX_NAME(init)
#define PFX_IOCTL     PREFIX_NAME(ioctl)
#define PFX_MVERSION  PREFIX_NAME(mversion)
#define PFX_OPEN      PREFIX_NAME(open)
#define PFX_READ      PREFIX_NAME(read)
#define PFX_REG       PREFIX_NAME(reg)
#define PFX_START     PREFIX_NAME(start)
#define PFX_UNLOAD    PREFIX_NAME(unload)
#define PFX_WRITE     PREFIX_NAME(write)
/**************************************************************************
| Debug display macros: one each for cmn_err calls with 0, 1, 2, 3 or 4
| arguments.  The macros generate the PREFIX_STRING, colon, space at the
| front of the message and \n on the end.  For example,
|       DBGMSG2("one %d two %x",a,b) is the same as
|       cmn_err(CE_DEBUG,"snoop_: one %d two %x\n",a,b)
|**************************************************************************/
#ifndef DEBUG
#define DBGMSG0(s)
#define DBGMSG1(s,x)
#define DBGMSG2(s,x,y)
#define DBGMSG3(s,x,y,z)
#define DBGMSG4(s,x,y,z,w)
#else
#define DBGMSGX(s) cmn_err(CE_DEBUG,PREFIX_STRING ": " s "\n"
#define DBGMSG0(s)         DBGMSGX(s) )
#define DBGMSG1(s,x)       DBGMSGX(s) ,x)
#define DBGMSG2(s,x,y)     DBGMSGX(s) ,x,y)
#define DBGMSG3(s,x,y,z)   DBGMSGX(s) ,x,y,z)
#define DBGMSG4(s,x,y,z,w) DBGMSGX(s) ,x,y,z,w)
#endif
/**************************************************************************
| The ERRMSGn macros are the same as the DGBMSGn macros, except they are
| always defined (not conditional on DEBUG) and use CE_WARN status.
|**************************************************************************/
#define ERRMSGX(s) cmn_err(CE_WARN,PREFIX_STRING ": " s "\n"
#define ERRMSG0(s)         ERRMSGX(s) )
#define ERRMSG1(s,x)       ERRMSGX(s) ,x)
#define ERRMSG2(s,x,y)     ERRMSGX(s) ,x,y)
#define ERRMSG3(s,x,y,z)   ERRMSGX(s) ,x,y,z)
#define ERRMSG4(s,x,y,z,w) ERRMSGX(s) ,x,y,z,w)
/**************************************************************************
| One instance of the following structure is created when any of our 
| devices is opened.  The structure is by default allocated in
| the node where the open() is executed.  The structure is protected by a 
| lock because it is possible for multiple threads in a pgroup to attempt
| concurrent read/write/ioctl calls to the same FD.
|    use_lock     : ensure only one thread modifies structure at a time
|    read_ptr     : address of data to return to read()
|    read_len     : length of data remaining to read()
|    v_current    : hwgraph vertex being snooped (initially /hw)
|    v_last_edge  : vertex at end of last-scanned edge
|    edge_place   : position in edge list, for /hw/snoop/edge
|    info_place   : position in the info list, for /hw/snoop/attr
|    hinv_place   : position in the hinv list, for /hw/snoop/hinv
|    scratch      : buffer to hold maximal /hw path on write() call
| Only one of the _place fields is used in any one structure, but the 
| memory saved by making a union of them is not worth the coding bother.
|**************************************************************************/
typedef struct snoop_user_s {
    mutex_t             use_lock;
    char *              read_ptr;
    unsigned int        read_len;
    vertex_hdl_t        v_current;
    vertex_hdl_t        v_last_edge;
    graph_edge_place_t  edge_place;
    graph_info_place_t  info_place;
    invplace_t          hinv_place;
    char scratch[HWGRAPH_VPATH_LEN_MAX*LABEL_LENGTH_MAX];
} snoop_user_t;
/**************************************************************************
| One instance of the following structure is created for each char device
| we create (3 in all), and its address is saved with device_info_set().
|    dev_lock   : for controlled access to the device data
|    val_func   : function to set up data for a read
|    nopen      : number of opened/allocated users
|    user_list  : vector of pointers to snoop_user structs
| Use of this structure is controlled by a reader/writer lock. Only the
| open & close entries modify the user list, and so claim the writer lock.
| Other entries claim it as readers.
|
| The reason for making user_list a fixed array (as opposed to linking
| the snoop_user structs in a chain) is because each snoop_user_t can be
| in a different module, and we want to touch only the one for the caller. 
|**************************************************************************/
#define MAX_PGID 20
typedef void (*val_func)(snoop_user_t *puser);
typedef struct snoop_base_s {
    rwlock_t        dev_lock;
    val_func        vector;
    unsigned        nopen;
    struct {
        pid_t user;         /* pgid at open() time */
        int generation;     /* number of occupants of this slot */
        snoop_user_t *work; /* -> corresponding work area */
    } user_list[MAX_PGID];
} snoop_base_t;
 
#ifdef __cplusplus
}
#endif
#endif /* _KERNEL */
#endif /* __SNOOP_H__ */
 

Driver Source

/**************************************************************************
|
| This is snoop.c, a pseudo-device driver for IRIX 6.4 and later.
|
| At snoop_init(), create three char device vertexes in the hwgraph,
| /hw/snoop/{edge,attr,hinv}.  Each device supports open, read, write,
| close, and ioctl.
|
| At most one open() from any process group is accepted for any device.
| Second attempts are rejected with EBUSY. However, multiple processes
| and POSIX threads in a process group may use the open FD concurrently.
|
| The driver maintains a current status for each process group open of
| each device. The two key status variables are:
|    a position on a current vertex in the hwgraph
|    a scan position for reading out edges, attributes, or inventory_t's
|
| Each read() of /hw/snoop/edge returns the next (first) edge from the
| current vertex as a character string. If the read length is less than
| the string length, the byte position is remembered and the rest of the
| string is returned on the next read.
|
| Each read() of /hw/snoop/attr returns the first/next attribute label
| from the current vertex under the same rule as edges.
|
| Each read() of /hw/snoop/hinv returns the first/next invent_t ditto.
| Note that an invent_t is binary data, not ascii. 
|
| For any device, a call to write() must present an absolute or relative
| path in the /hw filesystem. The device moves to the selected vertex
| and initializes the input scan of edges, attrs, or hinvs.  For example,
|     write(FD,"/hw/snoop")  moves to that vertex.
|     write(FD,"..") moves back to /hw
|     write(FD,"snoop/edge") moves down to /hw/snoop/edge.
|
| The following IOCTL calls are supported (declared in snoop.h):
|
|   IOCTL_MASTER_TEST  returns 0 if a "master" vertex exists, or ENOENT
|
|   IOCTL_MASTER_GO    moves the current vertex to its master, if any
|
|   IOCTL_PATH_READ    sets to return the complete "/hw..." path of the
|                      current vertex on the next read() call, in place
|                      of the next edge/attr/hinv.
|
|   IOCTL_CLOSING      notifies the driver that this process group is
|                      about to close the device. Subsequent attempts to
|                      use that open file are rejected. Interesting
|                      mutual-exclusion problems arise here.
|
|   IOCTL_VERTEX_GET   retrieve the current vertex handle.  Argument is
|                      an address in user memory to place the handle.
|
|   IOCTL_VERTEX_SET   set a new current vertex. Argument is an address
|                      in user memory where a handle sits, presumably
|                      one retrieved with IOCTL_VERTEX_GET.
|
|**************************************************************************/
#include "snoop.h"  /* all #includes are inside this header */
int PFX_DEVFLAG = D_MP;
char * PFX_MVERSION = M_VERSION;
/* Function Directory */
static int
    alloc_user(snoop_base_t *pbase);        /* make & init snoop_user_t on open */
static pid_t
    get_pgroup(void);                   /* get PGID of client */
static int
    get_user_index(snoop_base_t *pbase, pid_t pgroup);  /* get index of client */
static snoop_user_t *
    get_user(snoop_base_t *pbase);      /* locate snoop_user_t for client */
static int
    init_dev(char *name, vertex_hdl_t v_snoop, val_func func);
static void
    reset_scans(snoop_user_t *puser);   /* reset input scans for client */
static void
    val_attr(snoop_user_t *puser);      /* scan next attr for read() */
static void
    val_edge(snoop_user_t *puser);      /* scan next edge for read() */
static void
    val_hinv(snoop_user_t *puser);      /* scan next inventory_t for read() */
int
    PFX_INIT();                         /* init() entry point */
int
    PFX_OPEN(dev_t *devp, int oflag, int otyp, cred_t *crp);
int
    PFX_REG();                          /* reg() entry point */
int
    PFX_START();                        /* start() entry point */
int
    PFX_WRITE(dev_t dev, uio_t *uiop, cred_t *crp);
int
PFX_IOCTL(dev_t dev, int cmd, void *arg, int mode, cred_t *crp, int *rvalp);
/**************************************************************************
| Get the process group ID for the client process. The pgid is used as
| a key to search the user_list.
|**************************************************************************/
static pid_t
get_pgroup(void)
{
    ulong_t val = 0;
    (void)drv_getparm(PPGRP,&val);
    return (pid_t) val;
}
/**************************************************************************
| Get the index of the snoop_user_t for the client process in the
| user_list.  Return -1 if the specified pgid is not found. "Not Found"
| is the expected result when this function is called from the
| pfx_open() entry.  It is a possible result in other entry points, but
| only when the client calls ioctl(IOCTL_CLOSING) and then continues
| to use the file descriptor.
|**************************************************************************/
static int
get_user_index(snoop_base_t *pbase, pid_t pgid)
{
    int j;

    for (j=0 ;j<MAX_PGID;++j) {
        if (pbase->user_list[j].user == pgid)
            return j;
    }
    return -1;
}
/**************************************************************************
| Locate the snoop_user_t for the client process. The caller is assumed
| to hold pbase->dev_lock as reader at least.
|**************************************************************************/
static snoop_user_t *
get_user(snoop_base_t *pbase)
{
    snoop_user_t *puser = NULL;
    int j;
    if (-1 != (j = get_user_index(pbase, get_pgroup())) )
        puser = pbase->user_list[j].work;
    return puser;
}
/**************************************************************************
| Reset all three data scans for this user. Only one scan is actually in
| use on a given device, but it's less trouble to have a single function.
|**************************************************************************/
static void
reset_scans(snoop_user_t *puser)
{
    puser->read_len = 0;
    puser->v_last_edge = GRAPH_VERTEX_NONE;
    puser->edge_place = EDGE_PLACE_WANT_REAL_EDGES;
    puser->info_place = GRAPH_INFO_PLACE_NONE;
    puser->hinv_place = INVPLACE_NONE;
}
/**************************************************************************
| Allocate a snoop_user_t for the calling process and install it in the
| user_list.  The caller must hold dev_lock as a writer.  Errors:
|   if the calling pgroup already has this device open, EBUSY
|   if there is no open slot in the user_list, EMFILE
|   if kmem_alloc fails, ENOMEM
| Initialize the lock and all 3 data scans before setting the pointer.
| Increment the generation count of the slot.
|**************************************************************************/
static int
alloc_user(snoop_base_t *pbase)
{
    snoop_user_t *puser;
    pid_t pgroup = get_pgroup();
    int j = get_user_index(pbase,pgroup);

    if (j != -1) {
        DBGMSG0("rejecting open, pgid in list");
        return EBUSY;
    }
    for(j=0 ;j<MAX_PGID;++j) { /* find empty user_list slot */
        if (!(pbase->user_list[j].user)) break;
    }
    if (j>=MAX_PGID) {
        DBGMSG0("user list full at open");
        return EMFILE;
    }
    puser = kmem_alloc(sizeof(*puser),KM_SLEEP+KM_CACHEALIGN);
    if (!puser) {
        ERRMSG0("unable to allocate user struct at open");
        return ENOMEM;
    }
    MUTEX_INIT(&puser->use_lock,MUTEX_DEFAULT,PREFIX_STRING);
    puser->v_current = hwgraph_root; /* "/hw" vertex, see hwgraph.h */
    reset_scans(puser);
    pbase->user_list[j].user = pgroup;
    pbase->user_list[j].generation += 1;
    pbase->user_list[j].work = puser;
    DBGMSG3("user for pgid %d at 0x%x in slot %d",pgroup,puser,j);
    ++ pbase->nopen;
    DBGMSG1("   now %d open",pbase->nopen);
    return 0;
}
/**************************************************************************
| Set up the next edge label from the current vertex as the read data.
| If there is no next edge label, set up to return 0 bytes.
| This function is used for read() to the device /hw/snoop/edge.
| The caller, snoop_read(), has checked that puser->read == 0.
|**************************************************************************/
static void
val_edge(snoop_user_t *puser)
{
    graph_error_t err;
    if (puser->v_current != GRAPH_VERTEX_NONE) {
        err = hwgraph_edge_get_next(
                    puser->v_current,       /* in source vertex */
                    puser->scratch,         /* out big buffer for string */
                    &puser->v_last_edge,    /* out save destination vertex */
                    &puser->edge_place);    /* inout scan position */
        if (!err) {                         /* we got a string... */
            puser->read_ptr = puser->scratch;   /* ..set up as read data */
            puser->read_len = 1+strlen(puser->scratch); /* incl. null */
        }
        else {                              /* no edge string, leave len=0 */
            if (err != GRAPH_NOT_FOUND)     /* ..unexpected cause? */
                ERRMSG1("hwgraph_edge_get_next err %d", err);
        }
    }
}
/**************************************************************************
| Set up the next attr label from the current vertex as the read data.
| If there is no next attr label, set up to return 0 bytes.
| This function is used for read() to the device /hw/snoop/attr.
|**************************************************************************/
static void
val_attr(snoop_user_t *puser)
{
    graph_error_t err;
    arbitrary_info_t junk;
    
    if (puser->v_current != GRAPH_VERTEX_NONE) {
        err = hwgraph_info_get_next_LBL(
                    puser->v_current,       /* in source vertex */
                    puser->scratch,         /* out big buffer for string */
                    &junk,                  /* don't want the info ptr */
                    &puser->info_place);    /* inout scan position */
        if (!err) {                         /* we got a string... */
            puser->read_ptr = puser->scratch;   /* ..set up as read data */
            puser->read_len = 1+strlen(puser->scratch); /* incl. null */
        }
        else {                              /* no edge string, leave len=0 */
            if (err != GRAPH_NOT_FOUND)     /* ..unexpected cause? */
                ERRMSG1("hwgraph_info_get_next err %d\n", err);
        }
    }
}
/**************************************************************************
| Set up the next inventory_t from the current vertex as the read data.
| If there is no next data, set up to return 0 bytes.
| This function is used for read() to the device /hw/snoop/hinv.
|**************************************************************************/
static void
val_hinv(snoop_user_t *puser)
{
    graph_error_t err;
    inventory_t *invp;
    
    if (puser->v_current != GRAPH_VERTEX_NONE) {
        err = hwgraph_inventory_get_next(
                    puser->v_current,       /* in source vertex */
                    &puser->hinv_place,     /* inout scan position */
                    &invp );                /* out ->inventory_t */
        if (!err) {
            puser->read_ptr = (char*)invp;
            puser->read_len = sizeof(inventory_t);
        }
        else {                              /* no inv data, leave len=0 */
            if (err != GRAPH_NOT_FOUND)     /* ..unexpected cause? */
                ERRMSG1("hwgraph_info_get_next err %d\n", err);
        }
    }
}
/**************************************************************************
| At initialization time, create a char special device "/hw/snoop/<name>"
| The <name> is "edge," "attr," or "hinv."  v_snoop is the handle of the
| master node, expected to be "/hw/snoop."
|**************************************************************************/
static int
init_dev(char *name, vertex_hdl_t v_snoop, val_func func)
{
    graph_error_t err;
    vertex_hdl_t v_dev = GRAPH_VERTEX_NONE;
    snoop_base_t *pbase = NULL;
    /*
    || See if the device already exists.
    */
    err = hwgraph_edge_get(v_snoop,name,&v_dev);
    if (err != GRAPH_SUCCESS) { /* it does not. create it. */
        err = hwgraph_char_device_add(
                v_snoop,            /* starting vertex */
                name,               /* path, in this case just a name */
                PREFIX_STRING,      /* our driver prefix */
                &v_dev);            /* out: new vertex */
        if (err) {
            ERRMSG2("char_device_add(%s) error %d",name,err);
            return err;
        }
        DBGMSG2("created device %s, vhdl 0x%x",name,v_dev);
    }
    else
        DBGMSG2("found device %s, vhdl 0x%x",name,v_dev);
    /*
    || The device vertex exists. See if it already contains a snoop_base_t
    || from a previous load. If the vertex was only just created,
    || this returns NULL and we need to aallocate a base struct.
    */
    pbase = device_info_get(v_dev);
    if (!pbase) { /* no device info yet */
        pbase = kmem_zalloc(sizeof(*pbase),KM_SLEEP);
        if (!pbase) {
            ERRMSG0("failed to allocate base struct");
            return ENOMEM;
        }
        RW_INIT(&pbase->dev_lock, PREFIX_STRING);
    }
    DBGMSG1("   base struct at 0x%x",pbase);
    /*
    || This is a key step: on a reload, we must refresh the address
    || of the value function, which is different from when we last loaded.
    */
    pbase->vector = func;
    device_info_set(v_dev,pbase);
    return 0;
}
/**************************************************************************
| At the pfx_init() entry point we establish our hwgraph presence
| consisting of three character special devices.  The base path string
| is "/hw/snoop" by default, however we accept input from the 
| driver-administration interface.
| Unload/reload issues: hwgraph_path_add and hwgraph_char_device_add do
| not return error codes when called to add an existing path!  The only
| way to tell if our device paths exist already -- meaning we have been
| unloaded and reloaded -- is to test for them explicitly.
|**************************************************************************/
int
PFX_INIT()
{
    int err;
    char * path;
    vertex_hdl_t v_snoop;
    char testpath[256];
    char * admin;
    admin = device_driver_admin_info_get(PREFIX_STRING,ADMIN_LABEL);
    if (admin)
        path = admin;
    else
        path = "/snoop";
    /*
    || The following call returns success when the requested path
    || exists already, or when the path can be created at this time.
    */
    err = hwgraph_path_add(
                GRAPH_VERTEX_NONE,      /* start at /hw */
                path,                   /* this is the path */
                &v_snoop);              /* put vertex there */
    DBGMSG2("adding path %s returns %d",path,err);
    if (!err) err = init_dev("edge",v_snoop,val_edge);
    if (!err) err = init_dev("attr",v_snoop,val_attr);
    if (!err) err = init_dev("hinv",v_snoop,val_hinv);
    return err;
}
/**************************************************************************
| The pfx_start() entry point is only included to prove it is called.
|**************************************************************************/
int
PFX_START()
{
    DBGMSG0("start() entry point called");
    return 0;
}
/**************************************************************************
| The pfx_reg() entry point is only included to prove it is called.
|**************************************************************************/
int
PFX_REG()
{
    DBGMSG0("reg() entry point called");
    return 0;
}
/**************************************************************************
| The pfx_unload() entry point is not supposed to be called unless all
| uses of our devices have been closed and pfx_close called.  That had
| better be right, because there is no convenient way for us at this time
| to double-check.  If this was not a loadable driver, we could keep 
| static pointers to our snoop_base_t structures, and a static count of
| open files, for that matter.  However, static variables are zero'd 
| following a reload.  So those would only be good until the first
| unload/reload sequence.
|**************************************************************************/
int
PFX_UNLOAD()
{
    DBGMSG0("unload() entry point called");
    return 0;
}
/**************************************************************************
| At the pfx_open() entry point we allocate a work structure for the
| client process group, if possible.  This requires getting a writer lock
| on the dev_lock.  It is possible, in principle, for this entry point
| to be called while the init() entry point is still running, after the
| vertex has been created and before the device info has been stored.
| So in this entry point only, we check to make sure device info exists.
|**************************************************************************/
int
PFX_OPEN(dev_t *devp, int oflag, int otyp, cred_t *crp) {
    int ret;
    vertex_hdl_t v_dev = (vertex_hdl_t)*devp;
    snoop_base_t *pbase = device_info_get(v_dev);
 
    if (!pbase) return ENODEV;
 
    DBGMSG3("open(dev=0x%x, oflag=0x%x, otyp=0x%x...)",v_dev,oflag,otyp);
    RW_WRLOCK(&pbase->dev_lock);
    ret = alloc_user(pbase);
    RW_UNLOCK(&pbase->dev_lock);
    return ret;
}
/**************************************************************************
| The pfx_close() entry point is called only when >>all<< processes have
| closed a device.  The entire user_list array can be cleared out and
| any remaining snoop_user structs freed.
|**************************************************************************/
int
PFX_CLOSE(dev_t dev, int flag, int otyp, cred_t *crp)
{
    vertex_hdl_t v_dev = (vertex_hdl_t)dev;
    snoop_base_t *pbase = device_info_get(v_dev);
    unsigned j;
 
    DBGMSG2("close(dev=0x%x, %d opens)",v_dev,pbase->nopen);
    RW_WRLOCK(&pbase->dev_lock);
    for (j=0;j<MAX_PGID;++j) {
        if (pbase->user_list[j].user) {
            kmem_free(pbase->user_list[j].work,sizeof(snoop_user_t));
            pbase->user_list[j].user = 0;
            pbase->user_list[j].work = 0;
        }
    }
    pbase->nopen = 0;
    RW_UNLOCK(&pbase->dev_lock);
    return 0;
}
/**************************************************************************
| The pfx_read() entry point finds some data by calling the one (of 3)
| scan functions appropriate to this device.  If data is found, it is
| copied to the user buffer, up to min(data length, user buffer size).
| This function does not modify the snoop_base, so it needs only the
| reader lock. It does modify the snoop_user, so has to lock that because
| multiple user threads can read the same FD concurrently.
|**************************************************************************/ 
int
PFX_READ(dev_t dev, uio_t *uiop, cred_t *crp)
{
    vertex_hdl_t v_dev = (vertex_hdl_t)dev;
    snoop_base_t *pbase = device_info_get(v_dev);
    snoop_user_t *puser;
 
    RW_RDLOCK(&pbase->dev_lock); /* block out open, close on device */
    puser = get_user(pbase);
    if (!puser) { /* very unlikely */
        DBGMSG0("reject read - no user");
        RW_UNLOCK(&pbase->dev_lock);
        return EINVAL;
    }
    MUTEX_LOCK(&puser->use_lock,-1); /* block other threads from work area */
    DBGMSG2("read request %d bytes to 0x%x",
                    uiop->uio_resid,uiop->uio_iov->iov_base);
    if (0 == puser->read_len) { /* need to rustle up some data */
        pbase->vector(puser);
    }
    if (puser->read_len) { /* we have some data (now) */
        int j, ret;
        j = (uiop->uio_resid>puser->read_len)?puser->read_len:uiop->uio_resid;
        ret = uiomove(puser->read_ptr,j,UIO_READ,uiop);
        if (0==ret) {
            puser->read_len -= j;
            puser->read_ptr += j;
            DBGMSG1("   moved %d bytes",j);
        }
        else {
            ERRMSG1("error %d from uiomove",ret);
        }
    }
    else {
        DBGMSG0("    no data available");
    }
    MUTEX_UNLOCK(&puser->use_lock);
    RW_UNLOCK(&pbase->dev_lock);
    return 0;
}
/**************************************************************************
| The pfx_write() entry point accepts data into the scratch area. No matter
| what happens, the input scan on this user is going to be reset, so if
| there is residual data in the scratch area, it can be overwritten.
| All the write data is moved to scratch and treated as a hwgraph path.
| It can be absolute or relative to the current vertex.  We traverse
| to that vertex and if it is found, make it the current vertex.
|**************************************************************************/
int
PFX_WRITE(dev_t dev, uio_t *uiop, cred_t *crp)
{
    vertex_hdl_t v_dev = (vertex_hdl_t)dev;
    snoop_base_t *pbase = device_info_get(v_dev);
    snoop_user_t *puser;
    int ret = 0;
    int user_lock = 0;
    int len;
 
    RW_RDLOCK(&pbase->dev_lock); /* block out open, close on device */
    puser = get_user(pbase);
    if (!puser) { /* very unlikely */
        DBGMSG0("reject write - no user");
        ret = EINVAL;
    }
    if (!ret) { /* user (pgroup) is valid */
        len = uiop->uio_resid;
        DBGMSG2("write request %d bytes from 0x%x",
                                len,     uiop->uio_iov->iov_base);
        if (len >= sizeof(puser->scratch)) {
            ret = ENOSPC;
            DBGMSG0("   rejected, path too long");
        }
        else if (!len) { /* write for 0 bytes? */
            ret = EINVAL;
            DBGMSG0("   rejected, 0 length");
        }
 
    }
    if (!ret) { /* data length is acceptable */
        MUTEX_LOCK(&puser->use_lock,-1); /* block others from work area */
        user_lock = 1;      /* remember to unlock it */
        reset_scans(puser); /* now we lose scan positioning */
        ret = uiomove(puser->scratch,len,UIO_WRITE,uiop);
        if (0 == ret) {
            puser->scratch[len] = '\0'; /* terminate string */
        }
        else { /* couldn't move it? */
            ERRMSG1("error %d from uiomove",ret);
        }
    }
    if (!ret) { /* path data has been copied */
        vertex_hdl_t v_end;
        char * path = puser->scratch;
        if (*path == '/') { /* absolute path */
            v_end = hwgraph_path_to_vertex(path);
            if (v_end == GRAPH_VERTEX_NONE)
                ret = GRAPH_NOT_FOUND;
        }
        else { /* relative path to current vertex */
            ret = hwgraph_traverse(puser->v_current,path,&v_end);
        }
        if (!ret) { /* v_end is a valid endpoint */
            (void)hwgraph_vertex_unref(puser->v_current);
            puser->v_current = v_end;
        }
        else {
            DBGMSG2("lookup (%s) = %d",puser->scratch,ret);
            ret = ESPIPE; /* "illegal seek" */
        }
    }
    if (user_lock)
        MUTEX_UNLOCK(&puser->use_lock);
    RW_UNLOCK(&pbase->dev_lock);
    return ret;
}
/**************************************************************************
| the pfx_ioctl() entry point receives ioctl() calls. Cleverly, all the
| supported ioctl calls are designed to use no "arg" parameters, thus
| avoiding all questions of user ABI.
***************************************************************************/
int
PFX_IOCTL(dev_t dev, int cmd, void *arg, int mode, cred_t *crp, int *rvalp)
{
    vertex_hdl_t v_dev = (vertex_hdl_t)dev;
    snoop_base_t *pbase = device_info_get(v_dev);
    snoop_user_t *puser;
    vertex_hdl_t v_mast;
    int ret = 0;
 
    RW_RDLOCK(&pbase->dev_lock); /* block out open, close on device */
    puser = get_user(pbase);
    if (!puser) { /* very unlikely */
        DBGMSG0("reject ioctl - no user");
        RW_UNLOCK(&pbase->dev_lock);
        return (*rvalp = EINVAL);
    }
    MUTEX_LOCK(&puser->use_lock,-1); /* block out other threads on file */
    switch(cmd) {
        case IOCTL_MASTER_TEST: {
        /*
        || Request the master vertex and return either 0 or ENOENT.
        */
            v_mast = device_master_get(puser->v_current);
            if (v_mast == GRAPH_VERTEX_NONE)
                ret = ENOENT;
            DBGMSG1("IOCTL_MASTER_TEST: %d",ret);
            break;
        }
        case IOCTL_MASTER_GO: {
        /*
        || Request the master vertex and if we get it, make it current.
        */
            v_mast = device_master_get(puser->v_current);
            if (v_mast != GRAPH_VERTEX_NONE) {
                hwgraph_vertex_unref(puser->v_current);
                reset_scans(puser);
                puser->v_current = v_mast;
            }
            else
                ret = ENOENT;
            DBGMSG1("IOCTL_MASTER_GO: %d",ret);
            break;
        }
        case IOCTL_VERTEX_GET: {
        /*
        || <arg> is a pointer to user space where we store a vertex handle.
        */
            if (copyout(&puser->v_current,arg,sizeof(puser->v_current)))
                ret = EINVAL;
            DBGMSG2("IOCTL_VERTEX_GET(0x%x): %d",arg,ret);
            break;  
        }
        case IOCTL_VERTEX_SET: {
        /*
        || <arg> is a pointer to a vertex handle in user memory,
        || hopefully one retrieved with IOCTL_VERTEX_GET.
        || Use hwgraph_vertex_ref() for a quick validity check,
        || and make it the current vertex.
        */
            vertex_hdl_t temp;
            if (copyin(arg,&temp,sizeof(temp)))
                ret = EINVAL;
            if (!ret) { /* copy was ok */
                ret = hwgraph_vertex_ref(temp);
                if (ret==GRAPH_SUCCESS) { /* it's a real vertex */
                    (void) hwgraph_vertex_unref(puser->v_current);
                    puser->v_current = temp;
                    ret = 0;
                }
                else { /* bogus */
                    DBGMSG2("vertex_ref(%d) -> %d",temp,ret);
                    ret = EINVAL;
                }
            }
            DBGMSG2("IOCTL_VERTEX_SET(0x%x): %d",arg,ret);
            break;  
        }
        case IOCTL_PATH_READ: {
        /*
        || Request the "canonical name" of the current vertex. There are
        || cases in which that name cannot be formed, in which event we
        || return EBADF (seems logical).  Otherwise, we reset the input
        || scan and set the new path as the input.  The pfx_read() entry
        || will return this data until it is consumed.
        || This function has to reset the scans because it has to use
        || the generous puser->scratch buffer. The alternative is to
        || allocate an equally generous work area on the stack, and to
        || copy the result to scratch only when hwgraph_vertex_name_get
        || succeeds. However, one, it almost always succeeds, and two,
        || that would use too much driver stack space.
        */
            reset_scans(puser); /* ensure no pending data in scratch */
            ret = hwgraph_vertex_name_get(
                    puser->v_current,puser->scratch,sizeof(puser->scratch));
            if (!ret) {
                puser->read_ptr = puser->scratch;
                puser->read_len = 1+strlen(puser->scratch);
            }
            else { /* cannot work out path for current */
                DBGMSG1("hwgraph_vertex_name_get ret %d",ret);
                ret = EBADF;
            }
            DBGMSG1("IOCTL_PATH_READ: %d",ret);
            break;
        }
        case IOCTL_CLOSING: {
        /*
        || The client process (on behalf of its pgroup) promises to close
        || this device, permitting us to dispose of its work area in
        || advance of a call to pfx_close(), which only comes when all
        || clients close their files.
        ||   In order to free the work area we must be sure not only that
        || no other process is using it, but that no other process is
        || waiting on its lock! Do that by releasing both locks and
        || getting the base lock as Writer.  However, in the interval
        || after releasing the lock, strange things could happen!
        */
            pid_t pgroup = get_pgroup();
            int index_now = get_user_index(pbase,pgroup);
            int gen_now = pbase->user_list[index_now].generation;
            int index_then;
            
            MUTEX_UNLOCK(&puser->use_lock);
            RW_UNLOCK(&pbase->dev_lock);
            /*
            || Right here, another thread of the pgroup could call this
            || operation and complete it, leaving us holding a stale puser.
            || Even stranger, it could then CLOSE the device and
            || reOPEN it, ending up in a different, or even in the SAME,
            || slot of user_list.
            */
            RW_WRLOCK(&pbase->dev_lock); /* block all other use of dev */
            index_then = get_user_index(pbase,pgroup);
            if ((gen_now == pbase->user_list[index_now].generation)
            &&  (index_now == index_then)) { /* no races going on */
                kmem_free(puser,sizeof(snoop_user_t));
                pbase->user_list[index_now].user = 0;
                pbase->user_list[index_now].work = NULL;
                puser = NULL; /* don't try to unlock, it's gone... */
            }
            else
                ret=EBUSY;
            DBGMSG1("IOCTL_CLOSING: %d",ret);
            break;
        }
        default: {
            ret = EINVAL;
        }
    }
    if (puser) /* not IOCTL_CLOSING */
        MUTEX_UNLOCK(&puser->use_lock);
    RW_UNLOCK(&pbase->dev_lock);
    return (*rvalp = ret);
}

User Program Source

/*
|     usnoop [snoop_path]
|
| Elementary unit-test of the snoop_ device driver.
|
| 1. Open all three /hw/snoop devices. Use snoop_path, if given,
|    as the base path to the edge, attr, and hinv devices.
| 2. In a loop:
|    a. Prompt the user for a path. Quit on null input.
|    b. Use write() to position the edge device to the given path.
|    c. Use ioctl to position the other two devices to that vertex.
|    d. Use read() on all 3 and dump the results.
*/
#include <stdio.h>
#include <errno.h> /* for errno */
#include <sys/types.h> /* for vertex_hdl_t */
#include <sys/stat.h> /* wanted by open */
#include <fcntl.h>   /* open */
#include <unistd.h> /* for read, write */
#include <invent.h> /* for inventory_t */
#include "snoop.h" /* KERNEL is not defined */
#define SNOOPATH "/hw/snoop/"
#define BIG 65536
#define FAIL(x) {perror(x);fflush(stderr);return errno;}
static int edgeFD, attrFD, hinvFD; /* FD's of three devices */
int open3(char *snoop)
{ /* open all three special devices, store FD's */
    int ret;
    char snoopath[256];

    sprintf(snoopath,"%s/%s",snoop,"edge");
    edgeFD = open(snoopath, O_RDWR);
    if (-1 == edgeFD) FAIL("open edge");
    sprintf(snoopath,"%s/%s",snoop,"attr");
    attrFD = open(snoopath, O_RDWR);
    if (-1 == attrFD) FAIL("open attr");
    sprintf(snoopath,"%s/%s",snoop,"hinv");
    hinvFD = open(snoopath, O_RDWR);
    if (-1 == hinvFD) FAIL("open hinv");
    return 0;
}
int point3(char *hwpath)
{ /* position all 3 device FD's at the given path */
    int ret;
    unsigned long long v_targ;
    int len = strlen(hwpath); /* assumes nonzero length */
    ret = write(edgeFD,hwpath,len);
    if (-1 == ret) FAIL("write edge");
    /* read back the vhandle of each device - to see if we can */
    ret = ioctl(edgeFD,IOCTL_VERTEX_GET,&v_targ);
    if (ret) FAIL("ioctl(edge,IOCTL_VERTEX_GET)");
    ret = ioctl(attrFD,IOCTL_VERTEX_SET,&v_targ);
    if (ret) FAIL("ioctl(attr,IOCTL_VERTEX_SET)");
    ret = ioctl(hinvFD,IOCTL_VERTEX_SET,&v_targ);
    if (ret) FAIL("ioctl(hinv,IOCTL_VERTEX_SET)");
    return 0;
}
int dump3()
{ /* read all data from all 3 device FD's and display */
    int ret;
    int len;
    inventory_t *i;
    char buf[BIG];
    /* Read & display canonical path of current vertex */
    ret = ioctl(edgeFD,IOCTL_PATH_READ);
    if (ret) FAIL("ioctl(edge,IOCTL_PATH_READ)");
    puts("\nPath read:");
    len = read(edgeFD,buf,BIG);
    if (-1 == len) FAIL("read edge path");
    puts(buf);
    puts("\nEdges:");
    do { /* display all edges from current vertex */
        len = read(edgeFD,buf,BIG);
        if (-1 == len) FAIL("read edge");
        if (len) puts(buf);
    } while (len);
    puts("\nAttrs:");
    do { /* display all labelled attributes at this vertex */
        len = read(attrFD,buf,BIG);
        if (-1 == len) FAIL("read attr");
        if (len) puts(buf);
    } while (len);
    puts("\nHinv:");
    do { /* dump all inventory records at this vertex */
        len = read(hinvFD,buf,BIG);
        if (-1 == len) FAIL("read hinv");
        if (len)
        {
            i = (inventory_t *)&buf[0];
            printf("class:%d type%d controller:%d unit:%d state:%d\n",
        i->inv_class,i->inv_type,i->inv_controller,i->inv_unit,i->inv_state);
        }
    } while(len);
    return 0;
}
int main(int argc, char *argv[])
{
    int ret = 0;
    char ans[256];
    ret = open3((argc>1)?argv[1]:SNOOPATH);  
    while (0==ret)
    {
        printf("enter path: ");
        gets(ans);
        if (0==strlen(ans)) break;  
        ret = point3(ans);
        if (!ret) ret = dump3();
    }
    return ret;
}