Next | Prev | Up | Top | Contents | Index

Writing Other Driver Routines

In addition to entry points, your device driver may include other routines to handle interrupts from the device and to handle device initialization at boot time (see Table 2-3). You may also want your driver to include routines (such as drvstrategy) that are not strictly necessary but that simplify writing the standard entry point routines.

Interrupt and Initialization Handling Routines
RoutineDescription
intrProcesses a device interrupt after a transfer terminates, whether normally (upon completion) or abnormally (due to some error).
strategyPerforms block I/O.
edtinitInitializes the device at boot time. Same as init().
initInitializes the device at boot time. Same as edtinit().
haltShuts down the driver when the system shuts down.
startInitializes a device at system startup.
unloadCleans up a loadable kernel module.


intr - Process a Device Interrupt

When your device driver does a read or write, the driver usually puts itself to sleep while it waits for the transfer to complete. When the transfer terminates, whether normally (upon completion) or abnormally (due to some error), the device sends an interrupt to the CPU. When the system receives the interrupt from the device, it looks in your device driver for the drvintr() routine and executes that routine. Some devices can interrupt when the open count is zero. The interrupt still must be handled.

When the device I/O completes., the drvintr() routine awakens the sleeping process. Within the drvintr() routine, you can use the kernel function biodone() to awaken the sleeping process and report the status of the transfer (whether normal or error).

For a SCSI device, there must not be a drvintr() routine because the driver is a "soft" driver that does not interact directly with the hardware. Instead, a callback routine is often provided. This routine may be given any name, but it is often of the form drv_intr():

drv_intr(scsi_request_t *sp);
Arguments

sp

A pointer to a scsi_request_t type structure. (See the sample code in Chapter 5, "Writing a SCSI Device Driver," for an example of a drv_intr() routine written for a SCSI type device.) You must explicitly pass drv_intr() in the sr_notify member of the scsi_request_t structure allocated for the device.

strategy - Perform Block I/O

The drvstrategy() routine is not a character device driver entry point in the strictest sense (the user does not call it). However, when writing a device driver, you will probably want to write a drvstrategy() routine. Typically, you call the drvstrategy() routine from the drvread() and drvwrite() routines, through the physiock() kernel routine:

drvread (dev_t dev, uio_t *uiop, cred_t *crp)
{
    return physiock(drvstrategy, 0, dev, B_READ,
           nblocks, uiop);
}
drvwrite (dev_t dev, uio_t *uiop, cred_t *crp)
{
    return physiock(drvstrategy, 0, dev, B_WRITE,
           nblocks, uiop);
}
physiock() is a kernel routine that:

physiock() reports a data transfer as valid if the following conditions are met:

For more information, see the physiock(D3) man page.

Note: In IRIX 5.x and earlier, pages are 4 KB, and the default maximum DMA size is 4 MB; in IRIX 6.0, pages are 16 KB, and the default maximum DMA size is 16 MB. You can change the DMA size by modifying maxdmasz, in /var/sysgen/mtune/kernel, using page as the basic unit. For other ways to modify this parameter, see the systune(1M) man page. I/O larger than what is allowed by maxdmasz produces the UNIX error ENOMEM. See Appendix B, "SCSI Controller Error Messages". If the second argument is 0, physiock() then allocates an IRIX buffer header (a kernel-level structure of type buf) and primes it with appropriate transfer information; otherwise, physiock() uses the argument as a pointer to a buf_t. This structure encapsulates all the information of a single I/O transfer.

physiock() assigns the values of the following buf type structure members:

b_un.b_addr

Contains the kernel virtual address from which information is read or to which information is written.

b_flags

Contains a bit mask of flags that describe the transfer. B_BUSY is set to indicate that the buffer is in use for an I/O transfer. B_READ is set if the transfer is a read.

b_bcount

Contains the number of bytes to be transferred.

b_edev

Contains the major and minor device numbers.

b_blkno

Contains the device block number to be transferred.

b_resid

On completion, before calling biodone(), the driver must set this member to the number of bytes that were not transferred.

b_biodone

If nonzero, this is taken as a function pointer, and the routine in question is called from biodone(); all normal biodone() processing is skipped. b_biodone may also be set by the user.
Finally, physiock() calls drvstrategy() and hands it a pointer to this buf structure. (See a description of physiock (D3) in the IRIX Device Driver Reference Pages for more details on this kernel procedure.)

Synopsis

drvstrategy(struct buf_t *bp)
{
    <your code>
}
Your drvstrategy() routine programs the device for the transfer. The information it needs to do this is contained in the structure pointed to by bp. Typically, your drvstrategy() routine starts the I/O by programming appropriate registers. When drvstrategy() is done, control returns to physiock(). physiock() then calls biowait(), and the process sleeps until the transfer is complete.

Normally, your interrupt handler will call biodone(bp) on completion. But before calling biodone(), your driver must have saved the bp value passed to the strategy routine. (You must awaken the sleeping process even if there is some initial error condition.) In addition, your drvintr() routine must indicate the success of the transaction by updating the b_resid member of the buf_t type structure to contain the number of bytes that were not transferred, then move to the next page.

To handle any I/O errors that occur, use bioerror (bp, errno), where bp is a pointer to the buf_t type structure passed in as the first parameter of your drvstrategy(), and errno is the appropriate error number. bioerror() sets the members of the buffer header so that higher level code can detect the error and call geterror() to retrieve the error number from the structure.

Caution: Your drvintr() routine and the routines it calls must not try to access the uiop structure directly. The structure it gets might not belong to the process that made the I/O request.


edtinit and init - Initialize a Device

Most devices need some initialization at boot time. The system looks in the object module for the driver for either of two routines, drvinit() or drvedtinit(), then executes the appropriate routine to initialize the device. If you use the INCLUDE directive (in the /var/sysgen/system/irix.sm file) to add a device to the kernel, your driver must use the drvinit() routine to initialize the device at boot time. If you use the VECTOR directive, your routine must use the drvedtinit() routine to initialize the device at boot time.

Because you use the INCLUDE directive to include SCSI device drivers in the kernel, your drivers for SCSI devices must include a drvinit() routine if you want to initialize the device at boot time (in which case, no edtinit() call will be generated). See Chapter 5, "Writing a SCSI Device Driver," for a synopsis of the drvinit() routine.

Because you use the VECTOR directive to include VME device drivers in the kernel, your device drivers for VME devices must include a drvedtinit() routine to initialize the device at boot time. See Chapter 3, "Writing a VME Device Driver," for a synopsis of the drvedtinit() routine and a discussion of VME-bus address space and PIO mapping.

Most device drivers of the general memory-mapping model are for VME type devices. (See Chapter 7, "Writing Kernel-level General Memory-mapping Device Drivers.") Therefore, most device drivers of the general memory-mapping model are included in the kernel using the VECTOR directive. Your object module for this type of device driver usually contains a drvedtinit() routine.

Synopsis

void drvedtinit(struct edt *e);


halt - Shut Down the Driver When the System Shuts Down

The drvhalt() routine, if present, is called to shut the driver down when the system is shut down. After the drvhalt() routine is called, no more calls are made to the driver entry points.

This entry point is optional. The device driver can not assume that the interrupts are enabled. The driver must make sure that no interrupts are pending from its device and must inform the device that no more interrupts are to be generated.

Synopsis

void drvhalt(void);

Return Values

None


start - Initialize a Device at System Startup

The drvstart() routine is called at system boot time (after system services are available and interrupts have been enabled) to initialize drivers and the devices they control.

This entry point is optional. The start routine can perform the following types of activity:

A driver that needs to perform setup and initialization tasks that must take place before system services are available and interrupts are enabled must use the drvinit() routine to perform such tasks. The drvstart() routine must be used for all other initialization tasks.

Synopsis

void drvstart(void);
Return Values

None


unload - Clean Up a Loadable Kernel Module

The drvunload() routine handles any cleanup a loadable kernel module must perform before it can be unloaded dynamically from a running system.

This entry point is only required if a module is to be unloaded from the system. A loadable module's unload routine is defined in module-specific initialization code called wrapper code. The drvunload() routine can perform activities such as:

Synopsis

int drvunload(void);
Return Values

The drvunload() routine returns 0 for success or the appropriate error number.

Synchronization Constraints

The drvunload() routine must not sleep or call any functions that sleep, such as biowait(), delay(), psema(), or sleep().


Next | Prev | Up | Top | Contents | Index