[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


10    Implementing Character and Block Device Driver Interfaces

Character and block device drivers can contain the following sections:

The following sections explain how to implement or set up each of these interfaces.


[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


10.1    Implementing the open Interface

A device driver's open interface performs the tasks necessary to open a device and prepare it for I/O operations. The kernel calls the driver's open interface on behalf of applications that make an open system call on the device special file that corresponds to the driver.

The code associated with an open interface resides in the open and close device section of the device driver. You specify the entry point for the driver's open interface in a dsent structure. Section 5.4 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with an open interface.

To implement an open interface you must understand the dev_t data type, which is discussed in Section 8.1.1. The following list describes some typical tasks that you perform when implementing an open interface:

Your open interface will probably perform most of these tasks and, possibly, some additional ones. The following sections describe each of these tasks, using the /dev/none driver as an example.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


10.1.1    Setting Up the open Interface

The following code shows you how to set up an open interface, using the /dev/none driver as an example:

noneopen(dev, flag, format) dev_t dev; [1] int flag; [2] int format; [3] {


.
.
.

  1. Declares an argument that specifies the major and minor device numbers for a specific NONE device. The minor device number is used to determine the logical unit number for the NONE device that is to be opened. [Return to example]

  2. Declares an argument to contain flag bits from the file /usr/sys/include/sys/file.h. These flags indicate whether the device is being opened for reading, writing, or both. [Return to example]

  3. Declares an argument to contain a constant that identifies whether the device is a character or a block device. These constants are defined in /usr/sys/include/sys/mode.h. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


10.1.2    Performing the Tasks Associated with Opening the Device

The /dev/none driver's open interface (called noneopen) shows some of the typical tasks that a device driver's open interface can perform. Specifically, the /dev/none driver's open interface performs the following tasks: checks to ensure that the open is unique, marks the device as open, and returns ESUCCESS to indicate success. The following code implements the noneopen interface. The code also shows a call to the minor interface. See Writing Device Drivers: Advanced Topics for an example implementation of an open interface for disk and tape device drivers.


.
.
.

register int unit = minor(dev); [1] struct controller *ctlr = noneinfo[unit]; [2] struct none_softc *sc = &none_softc[unit]; [3]

if(unit >= num_none) return (ENODEV); [4]

if (sc->sc_openf == DN_OPEN) return (EBUSY); [5]

if ((ctlr !=0) && (ctlr->alive & ALV_ALIVE)) { sc->sc_openf = DN_OPEN; return(ESUCCESS); [6] }

else return(ENXIO); [7] }

  1. Declares a unit variable and initializes it to the device minor number. Note the use of the minor interface to obtain the device minor number.

    The minor interface takes one argument: the number of the device for which an associated device minor number will be obtained. The minor number is encoded in the dev argument, which is of type dev_t. [Return to example]

  2. Declares a pointer to a controller structure and calls it ctlr. The driver initializes ctlr to the controller structure associated with this NONE device. The minor device number, unit, is used as an index into the array of controller structures to determine which controller structure is associated with this NONE device. [Return to example]

  3. Declares a pointer to a none_softc structure and calls it sc. The driver initializes sc to the address of the none_softc structure associated with this NONE device. The minor device number, unit, is used as an index into the array of none_softc structures to determine which none_softc structure is associated with this NONE device. [Return to example]

  4. The NONE device requires no real work to open; therefore, the code could simply ignore the call and return ESUCCESS. To demonstrate some of the checking that a real driver might perform, the example provides code that checks to be sure that the device exists.

    If the device minor number, unit, is greater than or equal to the number of devices configured by the system, returns the error code ENODEV, which indicates there is no such device on the system. This error code is defined in /usr/sys/include/sys/errno.h.

    Section 7.1.6 shows how the noneprobe interface increments the num_none variable. [Return to example]

  5. If the sc_openf member of the sc pointer is equal to DN_OPEN, returns the error code EBUSY, which indicates that the NONE device has already been opened. This error code is defined in /usr/sys/include/sys/errno.h. This example test is used to ensure that this unit of the driver can be opened only once at a time. This type of open is referred to as an exclusive access open. [Return to example]

  6. If the ctlr pointer is not equal to 0 and the alive member of ctlr has the ALV_ALIVE bit set, then the device exists. If this is the case, the noneopen interface sets the sc_openf member of the sc pointer to the open bit DN_OPEN and returns ESUCCESS to indicate a successful open. [Return to example]

  7. If the device does not exist, noneopen returns the error code ENXIO, which indicates that the device does not exist. This error code is defined in /usr/sys/include/sys/errno.h. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


10.2    Implementing the close Interface

The open interface is called every time that any user initiates an action that invokes the open system call. The close interface, however, is called only when the last user initiates an action that closes the device. The reason for this difference is to allow the driver to take some special action when there is no work left to perform.

A device driver's close interface performs the tasks necessary to close a device that was previously opened by the driver's open interface. The kernel calls the driver's close interface on behalf of applications that make a close system call on the device special file that corresponds to the driver. This action occurs when the last file descriptor that is open and associated with this device is closed when the application calls the close system call.

The code associated with a close interface resides in the open and close device section of the device driver. You specify the entry point for the driver's close interface in a dsent structure. Section 5.4 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a close interface.

To implement a close interface you must understand the dev_t data type, which is discussed in Section 8.1.1. The following list describes some typical tasks that you perform when implementing a close interface:

Your close interface will probably perform most of these tasks and, possibly, some additional ones. The following sections describe each of these tasks, using the /dev/none driver as an example.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


10.2.1    Setting Up the close Interface

The following code shows you how to set up a close interface, using the /dev/none driver as an example:

noneclose(dev, flag, format) dev_t dev; [1] int flag; [2] int format; [3] {


.
.
.

  1. Like the noneopen interface, the noneclose interface declares an argument that specifies the major and minor numbers for a specific NONE device. The minor device number is used to determine the logical unit number for the NONE device to be closed. [Return to example]

  2. Like the noneopen interface, the noneclose interface also declares an argument to contain flag bits from the file /usr/sys/include/sys/file.h. Typically, a driver's close interface does not make use of this argument. [Return to example]

  3. Although the format argument is shown here, a driver's close interface does not typically make use of this argument. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


10.2.2    Performing the Tasks Associated with Closing the Device

The /dev/none driver's close interface (called noneclose) shows some of the typical tasks that a device driver's close interface can perform. Specifically, the /dev/none driver's close interface uses the same arguments as noneopen, gets the device minor number in the same way, and initializes the controller and none_softc structures identically. The purpose of noneclose is to turn off the open flag for the specified NONE device. The following code implements the noneclose interface. The code also shows calls to the following interfaces: write_io_port and mb. See Writing Device Drivers: Advanced Topics for an example implementation of a close interface for disk and tape device drivers.


.
.
.

register int unit = minor(dev); [1] struct controller *ctlr = noneinfo[unit]; [2] struct none_softc *sc = &none_softc[unit]; [3] register io_handle_t reg = (io_handle_t) ctlr->addr; [4]

write_io_port(reg + NONE_CSR, 4, 0, 0); [5]

mb(); [6]

sc->sc_openf = DN_CLOSE; [7]

return(ESUCCESS); [8] }

  1. Declares a unit variable and initializes it to the device minor number. Note the use of the minor interface to obtain the device minor number.

    The minor interface takes one argument: the number of the device for which an associated device minor number will be obtained. The minor number is encoded in the dev argument, which is of type dev_t. [Return to example]

  2. Declares a pointer to a controller structure and calls it ctlr. The driver initializes ctlr to the controller structure associated with this NONE device. The minor device number, unit, is used as an index into the array of controller structures to determine which controller structure is associated with this NONE device. [Return to example]

  3. Declares a pointer to a none_softc structure and calls it sc. The driver initializes sc to the address of the none_softc structure associated with this NONE device. The minor device number, unit, is used as an index into the array of none_softc structures to determine which none_softc structure is associated with this NONE device. [Return to example]

  4. Section 7.1.6 shows that the /dev/none device driver stored the I/O handle passed to its probe interface in the reg variable. The /dev/none device driver now initializes reg to the system virtual address (SVA) for the NONE device. This address is obtained from the addr member of the controller structure associated with this NONE device. Section 17.2.11 describes the addr member.

    Because the data types are different, this line performs a type-casting operation that converts the addr member (which is of type caddr_t) to be of type io_handle_t. [Return to example]

  5. The NONE device is not a real device and therefore would not initiate interrupts. However, this line shows how to turn off interrupts by writing the value zero (0) to the NONE device's control/status register. To accomplish the write operation, the /dev/none device driver calls the write_io_port interface.

    The write_io_port interface takes four arguments:

    [Return to example]

  6. Calls the mb interface to perform a memory barrier on Alpha CPUs. It is generally recommended that drivers operating on Alpha CPUs call the mb interface immediately after a call to write_io_port. See Section 3.1.6 for a detailed discussion of when to call the mb interface. [Return to example]

  7. Turns off the open flag by setting the sc_openf member of the sc pointer to the close bit DN_CLOSE. This action frees up the unit so that subsequent calls to noneopen will succeed. [Return to example]

  8. Returns ESUCCESS to indicate a successful close of the NONE device. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


10.3    Implementing the ioctl Interface

The ioctl interface typically performs all device-related operations other than read or write operations. A device driver's ioctl interface is called as a result of an ioctl system call. Only those ioctl commands that are device specific or that require action on the part of the device driver result in a call to the driver's ioctl interface. You specify the entry point for the driver's ioctl interface in a dsent structure. Section 5.4 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with an ioctl interface.

The following example shows a simple implementation of an ioctl interface, using the /dev/none driver as an example. The /dev/none driver's ioctl interface (called noneioctl) obtains and clears the count of bytes that was previously written by nonewrite. When a user program issues the command to obtain the count, the /dev/none driver returns the count through the data pointer passed to the noneioctl interface. When a user program asks to clear the count, the /dev/none driver does so. The code example also shows a call to the minor interface. See Writing Device Drivers: Advanced Topics for an example implementation of an ioctl interface for disk and tape device drivers.

noneioctl(dev, cmd, data, flag) dev_t dev; [1] unsigned int cmd; [2] caddr_t data; [3] int flag; [4]

{

int unit = minor(dev); [5] int *res; [6] struct none_softc *sc = &none_softc[unit]; [7]

res = (int *) data; [8]

if(cmd == DN_GETCOUNT) *res = sc->sc_count; [9]

if(cmd == DN_CLRCOUNT) sc->sc_count = 0; [10]

return (ESUCCESS); [11] }

  1. Declares an argument that specifies the major and minor device numbers for a specific NONE device. The minor device number is used to determine the logical unit number for the NONE device on which the ioctl operation is to be performed. [Return to example]

  2. Declares an argument to contain the ioctl command as specified in /usr/sys/include/sys/ioctl.h or in another include file that you define. [Return to example]

  3. Declares a pointer to the ioctl command-specified data that is to be passed to the device driver or filled in by the device driver. This argument is a kernel address. The size of the data cannot exceed the size of a page (currently 8 kilobytes (KB) on Alpha systems). At least 128 bytes are guaranteed. Any size between 128 bytes and the page size may fail if memory cannot be allocated. The particular ioctl command implicitly determines the action to be taken. The ioctl system call performs all the necessary copy operations to move data to and from user space. [Return to example]

  4. Declares an argument that holds the access mode of the device. The access modes are represented by flag constants defined in /usr/sys/include/sys/file.h. [Return to example]

  5. Declares a unit variable and initializes it to the device minor number. Note the use of the minor interface to obtain the device minor number.

    The minor interface takes one argument: the number of the device for which an associated device minor number will be obtained. The minor number is encoded in the dev argument, which is of type dev_t. [Return to example]

  6. Declares a pointer to a variable called res that will store a pointer to the kernel memory allocated by the ioctl system call. This variable also stores the character count. The nonewrite interface stores this character count in the sc_count member of the softc structure associated with this NONE device. Section 8.2.2 discusses the nonewrite interface. [Return to example]

  7. Declares a pointer to a none_softc structure and calls it sc. The driver initializes sc to the address of the none_softc structure associated with this NONE device. The minor device number, unit, is used as an index into the array of none_softc structures to determine which none_softc structure is associated with this NONE device. [Return to example]

  8. Sets the res variable to point to the kernel memory allocated by the ioctl system call. The ioctl system call copies the data to and from user address space.

    Because the data types are different, this line performs a type-casting operation that converts the data argument (which is of type caddr_t) to be of type pointer to an int. [Return to example]

  9. If the ioctl command is equal to the DN_GETCOUNT macro, sets res to the number of bytes in the write request. This count was previously set by the nonewrite interface in the sc_count member of the sc pointer. Section 5.1.2 discusses the DN_GETCOUNT macro. Section 8.2.2 discusses the nonewrite interface. [Return to example]

  10. If the ioctl command is equal to the DN_CLRCOUNT macro, sets the count of characters written to the device to the value zero (0). This line has the effect of clearing the sc_count member of the softc structure associated with this NONE device. Section 5.1.2 discusses the DN_CLRCOUNT macro. [Return to example]

  11. Returns ESUCCESS to indicate successful completion of the ioctl operation. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Chapter] [Index] [Help]


10.4    Implementing the Interrupt Handler

The interrupt section applies to both character and block device drivers and it contains a device interrupt handler, which this book refers to as the driver's interrupt handler. The interrupt handler is called as a result of a hardware interrupt.

Digital requires that all drivers call the handler interfaces to dynamically register interrupt handlers. Typically, a device driver registers interrupt handlers in the driver's probe interface. Section 7.1.6 discusses how to register a driver's interrupt handler (using the /dev/none driver as an example). Section 7.1.7 discusses how to register a driver's shared interrupt handler (using the /dev/xx driver as an example).

When the kernel interrupt interface that does the initial handling of interrupts receives an interrupt, it:

Upon return from the driver's interrupt handler, the kernel restores the state of the CPU to allow previously running processes to run.

The /dev/none driver's interrupt handler (called noneintr) is a stub because there is no physical device to generate an interrupt. However, most devices can generate interrupts. For example, a terminal might generate an interrupt when a character is keyed into it. There is an interrupt entry point for those drivers that are written for devices that generate interrupts. Section 2.1.3.4 discusses a number of important issues relating to interrupts. Some devices generate more than one type of interrupt. Thus, drivers controlling these devices can contain more than one interrupt section. For the /dev/none driver, these issues are not relevant because there are no interrupts, but they will be important for most drivers.

To give you an idea of some tasks an interrupt handler can perform, the following example shows the code associated with the /dev/xx driver's interrupt handler (called xxintr). The xxintr interrupt handler is an example of a shared interrupt.

int xx_intr(parameter)
   int parameter; [1]
{

.
.
.
register struct xx_softc *sc = xx_softc[unit]; [2] int intr_status; [3]

.
.
.
intr_status = xx_readio(PNV_INTR_CTRL, sc); [4] if (!(intr_status & PNV_INTR_OCCURRED)) return(INTR_NOT_SERVICED); [5] if ((intr_status & PNV_DMA_INTR) { [6] xx_writeio(PNV_INTR_CTRL, (intr_status & ~(PNV_CRD_INTR|PNV_ABRT_INTR)), sc);

.
.
.
} return(INTR_SERVICED); [7] } /* End of xx_intr */

  1. Specifies any parameter that the driver needs to control operation of the interrupt handler. The value that gets passed to the parameter argument is the value you stored in the param member of the handler_intr_info data structure.

    In this example, the device driver's interrupt handler does not make use of this argument. [Return to example]

  2. Specifies a pointer to a softc data structure and initializes it to the softc structure associated with this XX device. [Return to example]

  3. Declares a variable called intr_status that stores the bit associated with the interrupt controller register after the interrupt occurred. [Return to example]

  4. Calls the device driver's xx_readio interface, which reads the interrupt control register PNV_INTR_CTRL to determine if an interrupt occurred. (Assume that the /dev/xx driver implements the xx_readio interface to read the control register.) [Return to example]

  5. If there is no interrupt status bit set in the interrupt control register, returns the constant INTR_NOT_SERVICED to the kernel's interrupt dispatch code. This constant indicates that the driver's interrupt handler did not service the shared interrupt. [Return to example]

  6. If the interrupt control register is set to the PNV_DMA_INTR bit, then an interrupt occurred. Calls the device driver's xx_writeio interface to write the bit status to the interrupt control register. (Assume that the /dev/xx driver implements the xx_writeio interface to write the bit status.) [Return to example]

  7. Returns INTR_SERVICED to the kernel's interrupt dispatch code to indicate that the driver's interrupt handler serviced the shared interrupt. [Return to example]