Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
10.  Drivers for Character Devices Mapping Device Memory segmap() Entry Point  Previous   Contents   Next 
   
 

devmap() Entry Point

int xxdevmap(dev_t dev, devmap_cookie_t handle, offset_t off,
     size_t len, size_t *maplen, uint_t model);

This entry point is called to export device memory or kernel memory to user applications. devmap(9E) is called from devmap_setup(9F) inside segmap(9E) or on behalf of ddi_devmap_segmap(9F). See Chapter 12, Mapping Device and Kernel Memory and Chapter 13, Device Context Management for details.

Multiplexing I/O on File Descriptors

A thread sometimes needs to handle I/O on more than one file descriptor. One example is an application program that needs to read the temperature from a temperature-sensing device and then report the temperature to an interactive display. If the program makes a read request and there is no data available, it should not block waiting for the temperature before interacting with the user again.

The poll(2) system call provides users with a mechanism for multiplexing I/O over a set of file descriptors that reference open files. poll(2) identifies those file descriptors on which a program can send or receive data without blocking, or on which certain events have occurred.

To allow a program to poll a character driver, the driver must implement the chpoll(9E) entry point. Its syntax is:

int xxchpoll(dev_t dev, short events, int anyyet, short *reventsp,
     struct pollhead **phpp); 

The system calls chpoll(9E) when a user process issues a poll(2) system call on a file descriptor associated with the device. The chpoll(9E) entry point routine is used by non-STREAMS character device drivers that need to support polling.

In chpoll(9E), the driver must follow these rules:

  • Implement the following algorithm when the chpoll(9E) entry point is called:

if (events are satisfied now) {     
        *reventsp = mask of satisfied events;
} else {
        *reventsp = 0;
        if (!anyyet)
                *phpp = &local pollhead structure;
}
return (0);

xxchpoll() should check to see if certain events have occurred; see the chpoll(9E) man page. It should then return the mask of satisfied events by setting the return events in *reventsp.

If no events have occurred, the return field for the events is cleared. If the anyyet field is not set, the driver must return an instance of the pollhead structure. It is usually allocated in a state structure and should be treated as opaque by the driver. None of its fields should be referenced.

  • Call pollwakeup(9F) whenever a device condition of type events, listed in Example 10-11, occurs. This function should be called only with one event at a time. pollwakeup(9F) might be called in the interrupt routine when the condition has occurred.

Example 10-11 and Example 10-12 show how to implement the polling discipline and how to use pollwakeup(9F).


Example 10-11 chpoll(9E) Routine

static int
xxchpoll(dev_t dev, short events, int anyyet,
        short *reventsp, struct pollhead **phpp)
{
     uint8_t status;
     short revent;
     struct xxstate *xsp;

     xsp = ddi_get_soft_state(statep, getminor(dev));
     if (xsp == NULL)
             return (ENXIO);
     revent = 0;
     /*
        * Valid events are:
        * POLLIN | POLLOUT | POLLPRI | POLLHUP | POLLERR
        * This example checks only for POLLIN and POLLERR.
        */
     status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
     if ((events & POLLIN) && data available to read) {
            revent |= POLLIN;
     }
     if ((events & POLLERR) && (status & DEVICE_ERROR)) {
            revent |= POLLERR;
     }
     /* if nothing has occurred */
     if (revent == 0) {
            if (!anyyet) {
                *phpp = &xsp->pollhead;
            }
     }
       *reventsp = revent;
     return (0);
}

In Example 10-12, the driver can handle the POLLIN and POLLERR events. The driver first reads the status register to determine the current state of the device. The parameter events specifies which conditions the driver should check. If the appropriate conditions have occurred, the driver sets that bit in *reventsp. If none of the conditions have occurred and anyyet is not set, the address of the pollhead structure is returned in *phpp.


Example 10-12 Interrupt Routine Supporting chpoll(9E)

static u_int
xxintr(caddr_t arg)
{
       struct xxstate *xsp = (struct xxstate *)arg;
     uint8_t        status;
     normal interrupt processing
     ...
     status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
     if (status & DEVICE_ERROR) {
            pollwakeup(&xsp->pollhead, POLLERR);
     }
     if (just completed a read) {
            pollwakeup(&xsp->pollhead, POLLIN);
     }
     ...
     return (DDI_INTR_CLAIMED);
}

pollwakeup(9F) is usually called in the interrupt routine when a supported condition has occurred. The interrupt routine reads the status from the status register and checks for the conditions. It then calls pollwakeup(9F) for each event to possibly notify polling threads that they should check again. Note that pollwakeup(9F) should not be called with any locks held, as it could cause the chpoll(9E) routine to be entered, resulting in deadlock if that routine tries to grab the same lock.

Miscellaneous I/O Control

The ioctl(9E) routine is called when a user thread issues an ioctl(2) system call on a file descriptor associated with the device. The I/O control mechanism is a catchall for getting and setting device-specific parameters. It is frequently used to set a device-specific mode, either by setting internal driver software flags or by writing commands to the device. It can also be used to return information to the user about the current device state. In short, it can do whatever the application and driver need it to do.

ioctl() Entry Point (Character Drivers)

int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode,
     cred_t *credp, int *rvalp);

The cmd parameter indicates which command ioctl(9E) should perform. By convention, I/O control commands indicate the driver they belong to in bits 8-15 of the command (usually given by the ASCII code of a character representing the driver), and the driver-specific command in bits 0-7. They are usually created in the following way:

#define XXIOC        (`x' << 8)        /* `x' is a character representing */
                                       /* device xx */
#define XX_GET_STATUS                  (XXIOC | 1) /* get status register */
#define XX_SET_CMD                     (XXIOC | 2) /* send command */

The interpretation of arg depends on the command. I/O control commands should be documented (in the driver documentation or a manual page) and defined in a public header file, so that applications can determine the names, what they do, and what they accept or return as arg. Any data transfer of arg (into or out of the driver) must be performed by the driver.

Certain classes of devices such as frame buffers or disks must support standard sets of I/O control requests. These standard I/O control interfaces are documented in the Solaris 8 Reference Manual Collection. For example, fbio(7I) documents the I/O controls that frame buffers must support, and dkio(7I) documents standard disk I/O controls. See "Miscellaneous I/O Control" for more information on I/O control.

Drivers must use ddi_copyin(9F) to transfer arg data from the userland application to the kernel and ddi_copyout(9F) from kernel to userland. Failure to use ddi_copyin(9F) or ddi_copyout(9F) will result in panics on architectures that separate kernel and user address spaces, or if the user address has been swapped out.

ioctl(9E) is usually a switch statement with a case for each supported ioctl(9E) request.


Example 10-13 ioctl(9E) Routine

static int
xxioctl(dev_t dev, int cmd, intptr_t arg, int mode,
    cred_t *credp, int *rvalp)
{
     uint8_t                csr;
     struct xxstate         *xsp;

     xsp = ddi_get_soft_state(statep, getminor(dev));
     if (xsp == NULL) {
            return (ENXIO);
     }
     switch (cmd) {
     case XX_GET_STATUS:
           csr = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
           if (ddi_copyout(&csr, (void *)arg,
               sizeof (uint8_t), mode) != 0) {
                   return (EFAULT);
           }
           break;
     case XX_SET_CMD:
           if (ddi_copyin((void *)arg, &csr,
             sizeof (uint8_t), mode) != 0) {
                 return (EFAULT);
           }
           ddi_put8(xsp->data_access_handle, &xsp->regp->csr, csr);
           break;
     default:
           /* generic "ioctl unknown" error */
           return (ENOTTY);
     }
     return (0);
}

 
 
 
  Previous   Contents   Next