Character device drivers can contain the following sections:
The following sections explain how to implement or set up each of these interfaces.
A device driver's read interface performs the tasks necessary to read data from a device. The kernel calls the driver's read interface for character device drivers on behalf of applications that make a read system call. The code associated with a read interface resides in the read and write device section of the device driver. You specify the entry point for the driver's read interface in a dsent structure. Section 6.6.5.1 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a read interface.
To implement a read interface you must understand the dev_t data type and the uio data structure. Section 8.1.1 and Section 8.1.2 discuss these topics. Section 8.1.1 discusses how to set up a read interface.
The dev_t data type is a type definition of the major and minor device numbers. Device driver writers use the dev_t data type to represent a device's major and minor numbers. This data type is an abstraction of the internal representations of the major and minor numbers. Driver writers do not need to know how the system internally represents the major and minor numbers. To ensure maximum portability of the device driver, use the major interface to extract the major number portion of this internal representation and use the minor interface to extract the minor number portion of this internal representation.
The following character driver interfaces take a dev_t as the first argument: read, write, select, and mmap (memory map). The following sections describe how to use the major and minor interfaces.
To get the device major number, call the major interface. The following code fragment shows a call to this interface:
.
.
.
xxread(dev, uio, flag) dev_t dev; [1] struct uio *uio; int flag;
.
.
.
int major_number = major(dev); [2]
.
.
.
To get the device minor number, call the minor interface. The following code fragment shows a call to this interface:
xxread(dev, uio, flag) dev_t dev; [1] struct uio *uio; int flag;
.
.
.
int unit = minor(dev); [2]
.
.
.
The uio structure describes I/O, either single vector or multiple vectors. Typically, device drivers do not manipulate the members of this structure. However, the structure is presented here for the purpose of understanding the uiomove kernel interface, which operates on the members of the uio structure. Table 8-1 lists the members of the uio structure along with their associated data types that you might need to understand.
Member Name | Data Type |
uio_iov | struct iovec * |
uio_iovcnt | int |
uio_offset | off_t |
uio_segflg | enum uio_seg |
uio_resid | int |
uio_rw | enum uio_rw |
The following character driver interfaces take a pointer to a uio structure as an argument: read and write. The following sections discuss each of these members. Section 8.2.2 shows how the /dev/none driver's write interface uses the uio structure.
The uio_iov member specifies a pointer to the first iovec structure. The iovec structure has two members: one that specifies the address of the segment and another that specifies the size of the segment. The system allocates contiguous iovec structures for a given transfer.
The uio_iovcnt member specifies the number of iovec structures for this transfer.
The uio_offset member specifies the offset within the file.
The uio_segflg member specifies the segment type. This member can be set to one of the following values: UIO_USERSPACE (the segment is from the user data space), UIO_SYSSPACE (the segment is from the system space), or UIO_USERISPACE (the segment is from the user I space).
The uio_resid member specifies the number of bytes that still need to be transferred.
The uio_rw member specifies whether the transfer is a read or a write. This member is set by read and write system calls according to the corresponding field in the file descriptor. This member can be set to one of the following values: UIO_READ (read transfer), UIO_WRITE (write transfer), or UIO_AIORW (Alpha I/O read/write transfer).
The following code shows you how to set up a read interface, using the /dev/none driver as an example:
noneread(dev, uio, flag) dev_t dev; [1] struct uio *uio; [2] int flag; [3] { return (ESUCCESS); [4] }
.
.
.
A device driver's write interface performs the tasks necessary to write data to a device. The kernel calls the driver's write interface for character device drivers on behalf of applications that make a write system call. The code associated with a write interface resides in the read and write device section of the device driver. You specify the entry point for the driver's write interface in a dsent structure. Section 6.6.5.1 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a write interface.
To implement a write interface you must understand the dev_t data type and the uio data structure. Section 8.1.1 and Section 8.1.2 discuss these topics. The following list describes some typical tasks that you perform when implementing a write interface.
Your write 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.
The following code shows you how to set up a write interface, using the /dev/none driver as an example:
nonewrite(dev, uio, flag) dev_t dev; [1] struct uio *uio; [2] int flag; [3]
{
.
.
.
The following code shows how a driver's write interface copies data to a device, using the /dev/none driver as an example. The code shows some typical variable and structure declarations and the use of the minor and panic interfaces. The /dev/none driver's write interface (called nonewrite) copies data from the address space pointed to by the uio structure to the device. Upon a successful write, nonewrite returns the value zero (0) to the write system call.
int unit = minor(dev); [1] struct controller *ctlr = noneinfo[unit]; [2] struct none_softc *sc = &none_softc[unit]; [3] unsigned int count; [4] struct iovec *iov; [5]
while(uio->uio_resid > 0) { [6] iov = uio->uio_iov; [7] if(iov->iov_len == 0) { [8] uio->uio_iov++; uio->uio_iovcnt--; if(uio->uio_iovcnt < 0) [9] panic("none write"); continue; }
count = iov->iov_len; [10]
iov->iov_base += count; [11] iov->iov_len -= count; [12] uio->uio_offset += count; [13] uio->uio_resid -= count; [14]
sc->sc_count +=count; [15] } return (ESUCCESS); }
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. Section 8.1.1 describes the dev_t data type. [Return to example]
The panic interface takes one argument: the message you want the panic interface to display on the console terminal. [Return to example]
A device driver's reset interface performs the tasks necessary to force a device reset to place the device in a known state after a bus reset operation. The bus adapter code calls the driver's reset interface after completion of a bus reset operation. The code associated with a reset interface resides in the reset section of the device driver. You specify the entry point for a driver's reset interface in a dsent structure. Section 6.6.5.1 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a reset interface. The following section describes how to set up a reset inteface, using the /dev/xx driver as an example.
The following code shows you how to set up a reset interface, using the /dev/xx driver as an example:
xxreset(busnum) int busnum; [1] {
.
.
.
A device driver's select interface performs the tasks necessary to determine whether data is available for reading and whether space is available for writing data. The kernel calls a driver's select interface on behalf of applications that make a select system call. The code associated with a select interface resides in the select section of the device driver. You specify the entry point for a driver's select interface in a dsent structure. Section 6.6.5.1 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a select interface.
The following list describes some typical tasks that you perform when implementing a select interface.
Your select interface will probably perform most of these tasks and, possibly, some additional ones. The following sections describe each of these tasks, using the /dev/xx driver as an example.
The sel_queue data structure provides device driver writers with a generic queue of select events. You must initialize the links member by calling the queue_init interface prior to using the select_enqueue and select_dequeue interfaces. Section 8.4.3 and Section 8.4.4 show how the /dev/xx driver's select interface calls these interfaces.
Table 8-2 lists the members of the sel_queue structure along with their associated data types.
Member Name | Data Type |
links | struct queue_entry |
event | struct event * |
The links member specifies a queue_entry structure. This structure contains a generic doubly linked list (queue).
The event member specifies a pointer to an event structure. This structure is an opaque structure; that is, you do not reference it in your device driver.
The following code shows you how to set up a select interface (and a typical declaration of the sel_queue structure), using the /dev/xx driver as an example.
.
.
.
struct {
.
.
.
sel_queue_t * sel_q;
.
.
.
} xx_softc[NXX]; [1] xxselect(dev, events, revents, scanning) dev_t dev; [2] short *events; [3] short *revents; [4] int scanning; [5] {
One task that a device driver's select interface can perform is to poll for input reads. A device driver typically calls two kernel support interfaces when polling for input reads: select_enqueue and select_dequeue. In addition, a device driver calls the queue_init kernel support interface to initialize the specified queue. The minor interface is used to obtain the minor device number associated with a specific device. The following code shows you how to call these interfaces when polling for input reads, using the /dev/xx driver as an example.
.
.
.
xxcattach (ctlr) struct controller *ctlr;
.
.
.
queue_init(&sc->sel_q.links); [1]
.
.
.
int nread; [2] register int unit = minor(dev); [3] struct xx_softc *sc = &xx_softc[unit]; [4] if (*events & POLLNORM) { [5] if (scanning) { [6] nread = xxnread(dev); [7] if (nread > 0) *revents |= POLLNORM; [8] else select_enqueue(&sc->sel_q); [9] } else select_dequeue(&sc->sel_q); [10] }
.
.
.
The queue_init interface takes one argument: a pointer to a queue_entry structure. This structure contains a links member that specifies a queue_entry structure. This structure contains a generic doubly linked list (queue).
In the example, the links member passed to queue_init is referenced through the sel_q member of the softc structure. Section 8.4.2 shows the declaration of the /dev/xx driver's softc structure. [Return to example]
This interface takes one argument: a pointer to a sel_queue structure. You previously initialized the links member of this structure by calling the queue_init interface. The select_enqueue interface adds the current kernel thread to the list of kernel threads waiting for a select-related event on this XX device. When input is available, the select request can be completed.
At a different interface in the /dev/xx driver (typically, either the interrupt handler or an interface called by the interrupt handler), when new input is received on the device the driver calls the select_wakeup interface. The driver passes to it the same parameter passed to select_enqueue to notify the upper levels of the select system call that the user-level process that initiated the select request can now be notified that the driver has new input available to be read. [Return to example]
This interface takes one argument: a pointer to a sel_queue structure. [Return to example]
Another task that a device driver's select interface can perform is to poll for output writes. A device driver typically calls two kernel support interfaces when polling for output writes: select_enqueue and select_dequeue. (A device driver would have previously called the queue_init interface, typically in the driver's attach interface to initialize the specified queue.) The following code shows you how to call these interfaces when polling for output writes.
.
.
.
if (*events & POLLOUT) { [1] if (scanning) { [2] if (xxnwrite(dev)) [3] *revents |= POLLOUT; [4] else select_enqueue(&sc->sel_q); [5] } else select_dequeue(&sc->sel_q); [6] } return (ESUCCESS); [7] }
.
.
.
At a different interface in the /dev/xx driver (typically, either the interrupt handler or an interface called by the interrupt handler), after previous output transmission is completed the driver calls the select_wakeup interface. The driver passes a pointer to a sel_queue structure to notify the upper levels of the select system call that the user-level process that initiated the select request can now be notified that the driver has new output available to be written. [Return to example]
This interface takes one argument: a pointer to a sel_queue structure. [Return to example]
A device driver's (specifically, a terminal device driver's) stop interface performs the tasks necessary to suspend transmission on a specified line. The code associated with a stop interface resides in the stop section of the device driver. You specify the entry point for a driver's stop interface in a dsent structure. Section 6.6.5.1 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a stop interface. The following section describes how to set up the stop interface.
The following code shows you how to set up a stop interface, using the /dev/xx driver as an example:
xxstop(tp, flag) struct tty *tp; [1] int flag; [2] {
.
.
.
Alpha CPUs do not support an application's use of the mmap system call. Therefore, if you are writing a device driver that operates on such CPUs, you need to use a mechanism other than the memory map interface. The memory map section applies only to character device drivers, and it contains a memory map interface. The memory map interface is invoked by the kernel as the result of an application calling the mmap system call. You specify the entry for the driver's xxmmap interface in a dsent structure. Section 6.6.5.1 describes the dsent structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a memory map interface.
The following code shows you how to set up a memory map interface, using the /dev/xx driver as an example.
xxmmap(dev, offset, prot) dev_t dev; [1] off_t offset; [2] int prot; [3] {
.
.
.
}
Flag | Meaning |
PROT_READ | Pages can be read. |
PROT_WRITE | Pages can be written. |