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
Routine | Description |
---|
intr | Processes a device interrupt after a transfer terminates, whether normally (upon completion) or abnormally (due to some error). |
strategy | Performs block I/O. |
edtinit | Initializes the device at boot time. Same as init(). |
init | Initializes the device at boot time. Same as edtinit(). |
halt | Shuts down the driver when the system shuts down. |
start | Initializes a device at system startup. |
unload | Cleans 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:
- Verifies whether the requested transfer is valid by checking whether the offset is at or past the end of the device and verifying that the offset is a multiple of the block size (512).
- Sets up a buffer header that describes the transfer.
- Faults pages in and locks the pages involved in the I/O transfer so they cannot be swapped out.
- Calls the strategy routine passed by the first parameter.
- Sleeps until the transfer is complete and awakens when the driver's
I/O completion handler calls biodone().
- Performs the necessary cleanup and updates, then returns to the routine that called it.
physiock() reports a data transfer as valid if the following conditions are met:
- the specified data location exists on the device
- the user has specified a storage area that exists in user memory space
- the user-space storage area is large enough.
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:
- initialize data structures
- allocate buffers for private buffering schemes
- map the device into virtual address space
- initialize hardware
- initialize time-outs
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:
- Deallocate memory acquired for private data
- Cancel any outstanding itimeout() or bufcall() requests made by the module
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