Next | Prev | Up | Top | Contents | Index

Character and Block Entry Point Driver Routines

Currently, the standard names for entry points are as shown in Table 2-1:

Standard Entry Points
drvopen()drvclose()drvread()drvwrite()
drvinit()drvedinit()drvmmap()drvmap()
drvunload()drvunmap()drvpoll()drvioctl()
drvhalt()  

Your driver normally contains an entry point named for at least drvopen(), drvclose(), drvread(), and drvwrite(). See Table 2-2 for a somewhat fuller description of these entry points.

Entry Point Driver Routines
RoutineDescription
openThe kernel calls drvopen() when the user process issues an open() system call.
closeThe user process invokes the close() system call when it is finished with a device, but the system does not necessarily execute your drvclose() entry point for that device.
read or writeThe kernel executes the drvread() or drvwrite() entry point whenever a user process calls the read() or write() system calls
ioctlCharacter devices may include a "special function" entry point, drvioctl().
pollA character device driver may include a drvpoll() entry point so that users can use select() or poll() to poll the file descriptors opened on such devices.
mmap, map, and unmapThe System VR4.x mmap() function establishes a mapping between a process's virtual address space and a memory object. The IRIX device drvmmap(), drvmap(), and drvunmap() entry points are used in device drivers for memory-mapped devices. See the respective man pages for details.
devflagThis sets the bitmask of flags that specify the driver's characteristics to the system.

The arguments and expected return values of each driver entry point are described below. The examples use a generic driver prefix drv where appropriate.

Note: The names of the procedures in your driver must start with the letter prefix of up to 14 letters for the device as given in the master.d file. For instance, if you write a driver for a device called cdr, the names of the entry points (and all the other routines defined in the driver) must start with cdr--cdropen, cdrclose, cdrread, and so on. Procedures in this manual use the prefix drv.


open - Gain Access to a Device

The kernel calls the drvopen() routine when the user process issues an open() system call. You must write your drvopen() entry point so that it prepares the device for I/O operations.

Your code for the drvopen() routine must be able to handle requests from multiple processes and to make appropriate responses, depending on the current state of the device. For example, an exclusive user device may be in a busy or not busy state; or a multiuser device may be not in use and in need of initialization; or the same device may be in use, initialized, and able to handle more users or not.

Also, drivers need a way to determine the ABI (Application Binary Interface) of the current user process so they can properly interpret structures passed in for ioctls. By using the following defines, which give the driver the size of various entities in bytes, a function in usrabi returns an error if no user process is running or else copies the type size information into a structure provided by the caller. (See ddi.h for a definition of usrabi.) A good driver will handle all possibilities or, at least, assert() that 64-bit longs and pointers go togther.

typedef struct __userabi {
        short uabi_szint;
        short uabi_szlong;
        short uabi_szptr;
        short uabi_szlonglong;
} __userabi_t;
Synopsis

#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/ddi.h>
#include <sys/vmereg.h>    /* For VME drivers */

int drvopen   (dev_t *devp, int flag, int otyp, cred_t *crp)
{
    /* <your code> */
   return value;  /* 0 or value from errno.h */
}
Arguments

devp

Device major and minor numbers. Use getemajor() and geteminor() to extract the major and minor device numbers from this parameter. The minor number helps you identify which device of a multidevice controller is being opened.

Note: This is a pointer to a device.

flag

Mode argument from the open() system call. Your code must check flag for FREAD and FWRITE bits. Typically, flag tells your code why the user wants to open the device.

otyp

A flag that tells your code the class of the device that it must open. This is useful if your driver must handle both character and block devices. For character devices, this flag is usually OTYP_CHR, but OTYP_LYR is also possible.

Note: For each OTYP_LYR open, you will always get an OTYP_LYR close. If your close routine actually frees memory or clears driver data structures, you must track OTYP_LYR opens and closes separately. Ensure that all outstanding DMA operations have cleared prior to a free.

crp

A pointer to the user credential structure.
Returns

If the device cannot be opened in the way requested, your code for this entry point must return an appropriate error code from sys/errno.h.

Notes

If you want the driver to enforce mutual exclusion on a device, enforce it by having the drvopen() routine test to see whether the device is busy. This requires adding reference counting between your open() and close() routines, which must be protected. If the device is busy, it can sleep until completion of the current activity, then awaken.


close - Relinquish Access to a Device

The user program invokes the close() system call when it is finished with a device, but the system does not necessarily execute your drvclose() entry point for that device. The system executes the drvclose() entry point only after all processes that have opened the device have also called close().

If the device is opened frequently, you may not actually want drvclose() to free all the memory and other resources allocated to the open device.

Synopsis

#include  <sys/types.h>
#include  <sys/file.h>
#include  <sys/errno.h>
#include  <sys/open.h>
#include  <sys/cred.h>
#include  <sys/ddi.h>
#include  <sys/vmereg.h>

drvclose (dev_t dev, int flag, int otyp, cred_t *crp)
{
    <your code>
   return value;  /* 0 or value from errno.h */
}

dev

Device major and minor numbers. Use getemajor() and geteminor() to get the major and minor device numbers from this parameter. The minor number helps you identify which device of a multidevice driver is being closed.

flag

A mode argument from the close() system call. Your code must check flag for FREAD and FWRITE bits. Typically, flag tells your code why the user wants to close the device.

otyp

A flag that tells your code the class of the device that it must close. This is useful if your driver must handle both character and block devices. For character devices, this flag is usually OTYP_CHR, but OTYP_LYR is also possible.

crp

A pointer to the user credential structure.
Returns

If your code for drvclose encounters an error, it must return an appropriate error code from sys/errno.h. Even if it returns an error, your drvclose routine must really close the device--it won't be called again.


read or write - Read/Write Data from/to a Device

The kernel executes the drvread() or drvwrite() entry point whenever a user process calls the read() or write() system call. The following is an outline of what your driver entry points do:

  1. Validate the addresses.

  2. Protect the data from being paged out.

  3. Start up the data transfer.

  4. Set protection timeout.

  5. Sleep while the data transfers.

  6. Wake up when data transfer is complete.

  7. Check the status of the data transfer.

  8. Clear timers.

  9. Report the status of the data transfer.

  10. Return to user.
Because IRIX provides you with a rich set of powerful kernel functions, you can implement the above procedure in a number of ways, each sensitive to the particular strengths and limitations of the device you are controlling. However, not all methods of implementing the above procedure work for all devices. (For example, what works for non-DMA type devices does not always work for DMA-type devices if the user's virtual addresses are not currently mapped.)

Using the kernel functions physiock() and biodone() and your own drvstrategy() and drvintr() routines, you can write drvwrite() and drvread() points that are appropriate for all types of character devices (more on drvstrategy() later in this chapter).

Synopsis

#include  <sys/types.h>
#include  <sys/errno.h>
#include  <sys/uio.h>
#include  <sys/cred.h>
#include  <sys/vmereg.h>
#include  <sys/ddi.h>

drvread (dev_t dev, uio_t *uiop, cred_t *crp)
{
    <your code>
   return physiock(drvstrategy, 0, dev, B_READ,
    nblocks_uiocp);
}
drvwrite (dev_t dev, uio_t *uiop, cred_t *crp)
{
    <your code> (see above)
   return physiock(drvstrategy, 0, dev, B_WRITE,
    nblocks_uiocp);
}
Arguments

dev

Major and minor device numbers of the device involved in the read or write operation. Use getemajor() and geteminor() to extract this information from dev.

uiop

On entry, the uiop parameter contains a pointer to a uiop structure that contains, among other things, the location (uiop->uio_iov->iov_base) and size (uiop->uio_iov->iov_len) of the buffer in user space from which to read or to which to write information.

crp

A pointer to the user credential structure.
Returns

As with the drvopen() and drvclose() entry points, your code for the drvread() and the drvwrite() entry points must (when necessary) return appropriate error codes.


ioctl - Control a Character Device

Character devices may include a "special routine" entry point, drvioctl(). You can use this entry point to perform a number of device-dependent functions other than the standard operations (such as read and write). The kernel executes the drvioctl() entry point when a user program issues the ioctl() system call.

Synopsis

#include  <sys/types.h>
#include  <sys/file.h>
#include  <sys/cred.h>
#include  <sys/errno.h>
#include  <sys/ddi.h
#include  <sys/vme.h

drvioctl (dev_t dev, int cmd, void *arg, int mode,
           cred_t *crp, int *rvalp)
{
    <your code>
   return value;  /* 0 or value from errno.h */
}
Arguments

dev

Major and minor device numbers of the device it must handle. Use getemajor() and geteminor() to extract this information from dev.

cmd

This parameter is useful when you have more than one "special routine." The user cannot call these special routines directly. However, the user can call ioctl() with the appropriate value as its second parameter, and thus specify which special routine it wants. Within your code for the drvioctl() entry point, you must test the cmd parameter and take the appropriate action.

arg

This parameter can be used or ignored by your code as needed. Its type depends on the cmd argument. It can be either an integer value or a pointer to a device-specific data structure. (If it is a pointer, do not reference that address directly; instead, use copyin() or copyout() to retrieve the contents.)

Note: The size of int and pointer passed in can vary depending on the ABI outside a 64-bit kernel. See userabi and userabi_t. in "Device-special File".

mode

The file modes set when the device was opened. Your driver can use this information to determine whether the device was opened for reading or writing.

crp

A pointer to the user credential structure.

rvalp

Is a pointer to the return value for the calling process. The driver may elect to set the value if ioctl() succeeds. This is distinct from the errno return value of the drvioctl() function itself.
Returns

As with the other driver entry points, your code for the drvioctl() entry point must return an appropriate error code from sys/errno.h in case of an error.


poll - Poll Entry Point for a Non-STREAMS Character Driver

A character device driver may include a drvpoll() entry point so that users can use select() or poll() to poll the file descriptors opened on such devices. These system calls tell the user whether input from the device is available or whether output to the device is possible.

Synopsis

#include <sys/poll.h>
#include <sys/ddi.h>
#include <sys/errno.h>
#include <sys/types.h>

struct drvinfo {
    . . .
    struct pollhead *phead;         /* output poll queue */
} drvinfo[MAXUNITS];

drvpoll(dev, events, anyyet, reventsp, phpp)
        dev_t  dev;
        short  events;
        int    anyyet;
        short  *reventsp;
        struct pollhead **phpp
{
    *reventsp = events;

    if ((events & (POLLIN|POLLRDNORM)) && no input available ) {
            *reventsp &= ~(POLLIN|POLLRDNORM);
    }

    if ((events & (POLLOUT) && output not possible ) {
            *reventsp &= ~POLLOUT;
    }

    if ((events & (POLLPRI|POLLRDBAND) && no out of band data ) {
            *reventsp &= ~(POLLPRI|POLLRDBAND);
    }

    if (device error) {
        *reventsp = POLLERR;
        return 0;
    }
    if (!*reventsp) 
        return 0;

    if (!anyyet) {
        *phpp = drvinfo[getminor(dev)].phead;
        return 0;
    }
}
Arguments

dev

Major and minor device numbers of the device it must handle. Use getemajor() and geteminor() to extract this information from dev.

events

A mask that indicates the events being polled. The significance of the bits of this value is defined in sys/poll.h. When the driver's poll() entry point is called, the driver must verify whether any of the events requested in events have occurred.

anyyet

A flag that indicates whether the driver must return a pointer to its pollhead structure to the caller.[7] If none of the events is pending, the driver must check the anyyet flag and, if it is zero, store the address of the device's pollhead structure in the pointer pointed to by phpp.

reventsp

A pointer to a bitmask of the returned events satisfied. The driver must store the mask consisting of the subset of events that are pending in the short pointed to by reventsp. Note that this mask may be zero if none of the events is pending.

phpp

A pointer to a pointer to a pollhead structure (defined in
sys/poll.h).
A driver that supports polling must provide a pollhead structure for each minor device supported by the driver. Use phalloc() to allocate the pollhead structure. Use phfree() to free the structure.

When an event occurs, the driver must issue a call to pollwakeup(), passing it the event that occurred and the address of the pollhead structure associated with the device. For example, in the device interrupt routine, drvintr():

drvintr()
{
...
  if (input available)
    pollwakeup (drvinfo[getminor(dev)].phead, POLLIN, POLLRDNORM);
  if (output possible)
    pollwakeup (drvinfo[getminor(dev)].phead, POLLOUT);
...
Returns

drvpoll can return an error and "hang up" by returning POLLERR and POLLHUP. You cannot specify these events in *events on entry to drvpoll. If your code for drvpoll() encounters an error, it must return an appropriate error code from sys/errno.h.


map or unmap - Check Virtual Mapping for a Memory-mapped Device

Use the drvmap() and drvunmap() entry points in device drivers for memory-mapped devices. They are described in Chapter 3, "Writing a VME Device Driver," in greater detail.

Synopsis

Note: These routines are nonstandard to System VR4.x.

#include "sys/types.h"
#include "sys/region.h"
#include "sys/mman.h"

drvmap (dev_t dev,vhandl_t *vt,off_t off,
        int length,int prot)
{
    <your code>
   return value;  /* 0 or value from errno.h */
}
drvunmap (dev,vt)
          dev_t    dev;
          vhandl_t *vt;
{
    <your code>
   return value;  /* 0 or value from errno.h */
}
Arguments

dev

Major and minor device numbers of the device it must handle. Use getemajor() and geteminor() to extract this information from dev.

vt

A handle to the virtual space in the calling process to which the device is mapped. (The structure for the handle is subject to change, so do not attempt to reference the members of the structure pointed to by the handle directly.)

off

An offset to an address within the device memory. This address is the start of the device memory that the user wants your code to map into user space. (The user may not want to map in all of the device memory.)

length

The number of bytes to map.

prot

A description of the protection to apply to the region it maps in. The values for this parameter can be found in sys/man.h.

devflag - driver flags

Synopsis

#include <sys/conf.h>
#include <sys/ddi.h>
int drvdevflag = 0;
Every driver must define a global integer variable called drvdevflag. This variable contains a bitmask of flags used to specify the driver's characteristics to the system. (When drvdevflag is defined, UNIX SVR4 conventions apply; if it is not defined, UNIX SVR3 conventions apply.)

The valid flags that may be set in drvdevflag are:

D_MP

The driver is multithreaded (it handles its own locking and serialization).

D_WBACK

The driver writes back cache before calling its drvstrategy routine.

D_OLD

The driver uses the old-style interface (pre-5.0 release). This flag is not recommended for new work.
If no flags are set for the driver, then drvdevflag must be set to 0. If this is not done, then lboot will assume that this is an old-style driver, and it will set D_OLD flag as a default.


[7] Routines that return a pointer to the caller must verify the caller's ABI and return data of the correct type without inadvertent conversions.
Next | Prev | Up | Top | Contents | Index