Document Information
Preface
Part I Designing Device Drivers for the Solaris Platform
1. Overview of Solaris Device Drivers
2. Solaris Kernel and Device Tree
3. Multithreading
4. Properties
5. Managing Events and Queueing Tasks
Queueing Tasks
6. Driver Autoconfiguration
7. Device Access: Programmed I/O
8. Interrupt Handlers
9. Direct Memory Access (DMA)
10. Mapping Device and Kernel Memory
11. Device Context Management
12. Power Management
13. Hardening Solaris Drivers
14. Layered Driver Interface (LDI)
Part II Designing Specific Kinds of Device Drivers
15. Drivers for Character Devices
16. Drivers for Block Devices
17. SCSI Target Drivers
18. SCSI Host Bus Adapter Drivers
19. Drivers for Network Devices
20. USB Drivers
Part III Building a Device Driver
21. Compiling, Loading, Packaging, and Testing Drivers
22. Debugging, Testing, and Tuning Device Drivers
23. Recommended Coding Practices
Part IV Appendixes
A. Hardware Overview
B. Summary of Solaris DDI/DKI Services
C. Making a Device Driver 64-Bit Ready
D. Console Frame Buffer Drivers
Index
|
Managing Events
A system often needs to respond to a condition change such as
a user action or system request. For example, a device might issue a
warning when a component begins to overheat, or might start a movie player
when a DVD is inserted into a drive. Device drivers can use a
special message called an event to inform the system that a change
in state has taken place.
Introduction to Events
An event is a message that a device driver sends to interested entities
to indicate that a change of state has taken place. Events are implemented
in the Solaris OS as user-defined, name-value pair structures that are managed using
the nvlist* functions. (See the nvlist_alloc(9F) man page. Events are organized by vendor,
class, and subclass. For example, you could define a class for monitoring environmental conditions.
An environmental class could have subclasses to indicate changes in temperature, fan status,
and power. When a change in state occurs, the device notifies the driver. The
driver then uses the ddi_log_sysevent(9F) function to log this event in a queue
called sysevent. The sysevent queue passes events to the user level for handling
by either the syseventd daemon or syseventconfd daemon. These daemons send notifications
to any applications that have subscribed for notification of the specified event. Two methods for designers of user-level applications deal with events:
An application can use the routines in libsysevent(3LIB) to subscribe with the syseventd daemon for notification when a specific event occurs.
A developer can write a separate user-level application to respond to an event. This type of application needs to be registered with syseventadm(1M). When syseventconfd encounters the specified event, the application is run and deals with the event accordingly.
This process is illustrated in the following figure. Figure 5-1 Event Plumbing
Using ddi_log_sysevent() to Log Events
Device drivers use the ddi_log_sysevent(9F) interface to generate and log events with the
system.
ddi_log_sysevent() Syntax
ddi_log_sysevent() uses the following syntax: int ddi_log_sysevent(dev_info_t *dip, char *vendor, char *class,
char *subclass, nvlist_t *attr-list, sysevent_id_t *eidp, int sleep-flag); where: - dip
A pointer to the dev_info node for this driver.
- vendor
A pointer to a string that defines the driver's vendor. Third-party drivers should use their company's stock symbol or a similarly enduring identifier. Sun-supplied drivers use DDI_VENDOR_SUNW.
- class
A pointer to a string defining the event's class. class is a driver-specific value. An example of a class might be a string that represents a set of environmental conditions that affect a device. This value must be understood by the event consumer.
- subclass
A driver-specific string that represents a subset of the class argument. For example, within a class that represents environmental conditions, an event subclass might refer to the device's temperature. This value must be intelligible to the event consumer.
- attr-list
A pointer to an nvlist_t structure that lists name-value attributes associated with the event. Name-value attributes are driver-defined and can refer to a specific attribute or condition of the device. For example, consider a device that reads both CD-ROMs and DVDs. That device could have an attribute with the name disc_type and the value equal to either cd_rom or dvd. As with class and subclass, an event consumer must be able to interpret the name-value pairs. For more information on name-value pairs and the nvlist_t structure, see Defining Event Attributes, as well as the nvlist_alloc(9F) man page. If the event has no attributes, then this argument should be set to NULL.
- eidp
The address of a sysevent_id_t structure. The sysevent_id_t structure is used to provide a unique identification for the event. ddi_log_sysevent(9F) returns this structure with a system-provided event sequence number and time stamp. See the ddi_log_sysevent(9F) man page for more information on the sysevent_id_t structure.
- sleep-flag
A flag that indicates how the caller wants to handle the possibility of resources not being available. If sleep-flag is set to DDI_SLEEP, the driver blocks until the resources become available. With DDI_NOSLEEP, an allocation will not sleep and cannot be guaranteed to succeed. If DDI_ENOMEM is returned, the driver would need to retry the operation at a later time. Even with DDI_SLEEP, other error returns are possible with this interface, such as system busy, the syseventd daemon not responding, or trying to log an event in interrupt context.
Sample Code for Logging Events
A device driver performs the following tasks to log events:
The following example demonstrates how to use ddi_log_sysevent(). Example 5-1 Calling ddi_log_sysevent()char *vendor_name = "DDI_VENDOR_JGJG"
char *my_class = "JGJG_event";
char *my_subclass = "JGJG_alert";
nvlist_t *nvl;
/* ... */
nvlist_alloc(&nvl, nvflag, kmflag);
/* ... */
(void) nvlist_add_byte_array(nvl, propname, (uchar_t *)propval, proplen + 1);
/* ... */
if (ddi_log_sysevent(dip, vendor_name, my_class,
my_subclass, nvl, NULL, DDI_SLEEP)!= DDI_SUCCESS)
cmn_err(CE_WARN, "error logging system event");
nvlist_free(nvl);
Defining Event Attributes
Event attributes are defined as a list of name-value pairs. The Solaris DDI
provides routines and structures for storing information in name-value pairs. Name-value pairs are
retained in an nvlist_t structure, which is opaque to the driver. The value
for a name-value pair can be a Boolean, an int, a byte, a
string, an nvlist, or an array of these data types. An int
can be defined as 16 bits, 32 bits, or 64 bits and can
be signed or unsigned. The steps in creating a list of name-value pairs are as follows.
Create an nvlist_t structure with nvlist_alloc(9F). The nvlist_alloc() interface takes three arguments:
nvlp – Pointer to a pointer to an nvlist_t structure
nvflag – Flag to indicate the uniqueness of the names of the pairs. If this flag is set to NV_UNIQUE_NAME_TYPE, any existing pair that matches the name and type of a new pair is removed from the list. If the flag is set to NV_UNIQUE_NAME, then any existing pair with a duplicate name is removed, regardless of its type. Specifying NV_UNIQUE_NAME_TYPE allows a list to contain two or more pairs with the same name as long as their types are different, whereas with NV_UNIQUE_NAME only one instance of a pair name can be in the list. If the flag is not set, then no uniqueness checking is done and the consumer of the list is responsible for dealing with duplicates.
kmflag – Flag to indicate the allocation policy for kernel memory. If this argument is set to KM_SLEEP, then the driver blocks until the requested memory is available for allocation. KM_SLEEP allocations might sleep but are guaranteed to succeed. KM_NOSLEEP allocations are guaranteed not to sleep but might return NULL if no memory is currently available.
Populate the nvlist with name-value pairs. For example, to add a string, use nvlist_add_string(9F). To add an array of 32-bit integers, use nvlist_add_int32_array(9F). The nvlist_add_boolean(9F) man page contains a complete list of interfaces for adding pairs.
To deallocate a list, use nvlist_free(9F). The following code sample illustrates the creation of a name-value list. Example 5-2 Creating and Populating a Name-Value Pair Listnvlist_t*
create_nvlist()
{
int err;
char *str = "child";
int32_t ints[] = {0, 1, 2};
nvlist_t *nvl;
err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); /* allocate list */
if (err)
return (NULL);
if ((nvlist_add_string(nvl, "name", str) != 0) ||
(nvlist_add_int32_array(nvl, "prop", ints, 3) != 0)) {
nvlist_free(nvl);
return (NULL);
}
return (nvl);
} Drivers can retrieve the elements of an nvlist by using a lookup function
for that type, such as nvlist_lookup_int32_array(9F), which takes as an argument the name
of the pair to be searched for.
Note - These interfaces work only if either NV_UNIQUE_NAME or NV_UNIQUE_NAME_TYPE is specified when
nvlist_alloc(9F) is called. Otherwise, ENOTSUP is returned, because the list cannot contain
multiple pairs with the same name.
A list of name-value list pairs can be placed in contiguous memory. This
approach is useful for passing the list to an entity that has
subscribed for notification. The first step is to get the size of the
memory block that is needed for the list with nvlist_size(9F). The next step
is to pack the list into the buffer with nvlist_pack(9F). The consumer
receiving the buffer's content can unpack the buffer with nvlist_unpack(9F). The functions for manipulating name-value pairs are available to both user-level and kernel-level
developers. You can find identical man pages for these functions in both man pages section 3: Library Interfaces and Headers
and in man pages section 9: DDI and DKI Kernel Functions. For a list of functions that operate on name-value pairs,
see the following table. Table 5-1 Functions for Using Name-Value PairsMan Page |
Purpose / Functions |
nvlist_add_boolean(9F) |
Add name-value pairs to the list.
Functions include: nvlist_add_boolean(), nvlist_add_boolean_value(), nvlist_add_byte(), nvlist_add_int8(), nvlist_add_uint8(), nvlist_add_int16(), nvlist_add_uint16(), nvlist_add_int32(), nvlist_add_uint32(), nvlist_add_int64(), nvlist_add_uint64(),
nvlist_add_string(), nvlist_add_nvlist(), nvlist_add_nvpair(), nvlist_add_boolean_array(), nvlist_add_int8_array, nvlist_add_uint8_array(), nvlist_add_nvlist_array(), nvlist_add_byte_array(), nvlist_add_int16_array(), nvlist_add_uint16_array(), nvlist_add_int32_array(), nvlist_add_uint32_array(), nvlist_add_int64_array(),
nvlist_add_uint64_array(), nvlist_add_string_array() |
nvlist_alloc(9F) |
Manipulate the name-value list buffer. Functions include: nvlist_alloc(), nvlist_free(), nvlist_size(), nvlist_pack(), nvlist_unpack(),
nvlist_dup(), nvlist_merge() |
nvlist_lookup_boolean(9F) |
Search for name-value pairs. Functions include: nvlist_lookup_boolean(), nvlist_lookup_boolean_value(), nvlist_lookup_byte(), nvlist_lookup_int8(), nvlist_lookup_int16(), nvlist_lookup_int32(),
nvlist_lookup_int64(), nvlist_lookup_uint8(), nvlist_lookup_uint16(), nvlist_lookup_uint32(), nvlist_lookup_uint64(), nvlist_lookup_string(), nvlist_lookup_nvlist(), nvlist_lookup_boolean_array, nvlist_lookup_byte_array(), nvlist_lookup_int8_array(), nvlist_lookup_int16_array(), nvlist_lookup_int32_array(), nvlist_lookup_int64_array(),
nvlist_lookup_uint8_array(), nvlist_lookup_uint16_array(), nvlist_lookup_uint32_array(), nvlist_lookup_uint64_array(), nvlist_lookup_string_array(), nvlist_lookup_nvlist_array(), nvlist_lookup_pairs() |
nvlist_next_nvpair(9F) |
Get name-value pair data. Functions include: nvlist_next_nvpair(),
nvpair_name(), nvpair_type() |
nvlist_remove(9F) |
Remove name-value pairs. Functions include: nv_remove(), nv_remove_all() |
|