Part I Designing Device Drivers for the Solaris Platform
1. Overview of Solaris Device Drivers
2. Solaris Kernel and Device Tree
5. Managing Events and Queueing Tasks
Device Instances and Instance Numbers
Lock Variable and Conditional Variable Initialization
Registering a Device-Supplied ID
7. Device Access: Programmed I/O
10. Mapping Device and Kernel Memory
14. Layered Driver Interface (LDI)
Part II Designing Specific Kinds of Device Drivers
15. Drivers for Character Devices
18. SCSI Host Bus Adapter Drivers
19. Drivers for Network Devices
Part III Building a Device Driver
21. Compiling, Loading, Packaging, and Testing Drivers
22. Debugging, Testing, and Tuning Device Drivers
23. Recommended Coding Practices
B. Summary of Solaris DDI/DKI Services
C. Making a Device Driver 64-Bit Ready
To support autoconfiguration, drivers are required to statically initialize the following data structures:
The data structures in Figure 5-1 are relied on by the driver. These structures must be provided and be initialized correctly. Without these data structures, the driver might not load properly. As a result, the necessary routines might not be loaded. If an operation is not supported by the driver, the address of the nodev(9F) routine can be used as a placeholder. In some instances, the driver supports the entry point and only needs to return success or failure. In such cases, the address of the routine nulldev(9F) can be used.
Note - These structures should be initialized at compile-time. The driver should not access or change the structures at any other time.
static struct modlinkage xxmodlinkage = { MODREV_1, /* ml_rev */ &xxmodldrv, /* ml_linkage[] */ NULL /* NULL termination */ };
The first field is the version number of the module that loads the subsystem. This field should be MODREV_1. The second field points to driver's modldrv structure defined next. The last element of the structure should always be NULL.
static struct modldrv xxmodldrv = { &mod_driverops, /* drv_modops */ "generic driver v1.1", /* drv_linkinfo */ &xx_dev_ops /* drv_dev_ops */ };
This structure describes the module in more detail. The first field provides information regarding installation of the module. This field should be set to &mod_driverops for driver modules. The second field is a string to be displayed by modinfo(1M). The second field should contain sufficient information for identifying the version of source code that generated the driver binary. The last field points to the driver's dev_ops structure defined in the following section.
static struct dev_ops xx_dev_ops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ xxgetinfo, /* devo_getinfo: getinfo(9E) */ nulldev, /* devo_identify: identify(9E) */ xxprobe, /* devo_probe: probe(9E) */ xxattach, /* devo_attach: attach(9E) */ xxdetach, /* devo_detach: detach(9E) */ nodev, /* devo_reset */ &xx_cb_ops, /* devo_cb_ops */ NULL, /* devo_bus_ops */ &xxpower /* devo_power: power(9E) */ };
The dev_ops(9S) structure enables the kernel to find the autoconfiguration entry points of the device driver. The devo_rev field identifies the revision number of the structure. This field must be set to DEVO_REV. The devo_refcnt field must be initialized to zero. The function address fields should be filled in with the address of the appropriate driver entry point, except in the following cases:
Set the devo_identify field to nulldev(9F). The identify() entry point is obsolete.
Set the devo_probe field to nulldev(9F) if a probe(9E) routine is not needed.
Set the devo_reset field to nodev(9F). The nodev() function returns ENXIO.
Set the devo_power field to NULL if a power() routine is not needed. Drivers for devices that provide Power Management functionality must have a power(9E) entry point. See Chapter 12, Power Management.
The devo_cb_ops member should include the address of the cb_ops(9S) structure. The devo_bus_ops field must be set to NULL.
static struct cb_ops xx_cb_ops = { xxopen, /* open(9E) */ xxclose, /* close(9E) */ xxstrategy, /* strategy(9E) */ xxprint, /* print(9E) */ xxdump, /* dump(9E) */ xxread, /* read(9E) */ xxwrite, /* write(9E) */ xxioctl, /* ioctl(9E) */ xxdevmap, /* devmap(9E) */ nodev, /* mmap(9E) */ xxsegmap, /* segmap(9E) */ xxchpoll, /* chpoll(9E) */ xxprop_op, /* prop_op(9E) */ NULL, /* streamtab(9S) */ D_MP | D_64BIT, /* cb_flag */ CB_REV, /* cb_rev */ xxaread, /* aread(9E) */ xxawrite /* awrite(9E) */ };
The cb_ops(9S) structure contains the entry points for the character operations and block operations of the device driver. Any entry points that the driver does not support should be initialized to nodev(9F). For example, character device drivers should set all the block-only fields, such as cb_stategy, to nodev(9F). Note that the mmap(9E) entry point is maintained for compatibility with previous releases. Drivers should use the devmap(9E) entry point for device memory mapping. If devmap(9E) is supported, set mmap(9E) to nodev(9F).
The streamtab field indicates whether the driver is STREAMS-based. Only the network device drivers that are discussed in Chapter 19, Drivers for Network Devices are STREAMS-based. All non-STREAMS-based drivers must set the streamtab field to NULL.
The cb_flag member contains the following flags:
The D_MP flag indicates that the driver is safe for multithreading. The Solaris OS supports only thread-safe drivers so D_MP must be set.
The D_64BIT flag causes the driver to use the uio_loffset field of the uio(9S) structure. The driver should set the D_64BIT flag in the cb_flag field to handle 64-bit offsets properly.
The D_DEVMAP flag supports the devmap(9E) entry point. For information on devmap(9E), see Chapter 10, Mapping Device and Kernel Memory.
cb_rev is the cb_ops structure revision number. This field must be set to CB_REV.