Writing Device Drivers

Chapter 4 Properties and Events

Properties and events are user-defined, name-value pair structures that are managed using the DDI/DKI interfaces. This chapter provides information on the following subjects:

Device Properties

Device attribute (or device-related) information can be represented by a name-value pair notation called a property.

A reg property, for example, represents device registers and onboard memory. The reg property is a software abstraction that describes device hardware registers; its value encodes the device register address location and size. Drivers use the reg property to access device registers.

Another example is the interrupt property. An interrupt property represents the device interrupt. Its value encodes the device-interrupt PIN number.

The value of a property can be one of five types:

A property that has no value is known as a Boolean property. It is considered to be true if it exists and false if it doesn't exist.

Device Property Names

Strictly speaking, DDI/DKI software property names are not restricted in any way; however, there are certain recommended uses. As defined in IEEE 1275-1994 (the Standard for Boot Firmware), a property “is a human readable text string consisting of one to thirty-one printable characters. Property names shall not contain upper case characters or the characters "/", "\", ":", "[", "]" and "@". Property names beginning with the character "+" are reserved for use by future revisions of IEEE 1275-1994.” By convention, underscores are not used in property names; use a hyphen (-) instead. Also by convention, property names ending with the question mark character (auto-boot?) contain values that are strings, typically TRUE or FALSE.

Predefined property names are listed in publications of the IEEE 1275 Working Group. See http://playground.sun.com/1275 for information on how to obtain these publications. For a discussion of adding properties in driver configuration files, see driver.conf(4). The pm(9P) and pm-components(9P) man pages shows how properties are used in power management. Read the sd(7D) man page as an example of how properties should be documented in device driver man pages.

Creating and Updating Properties

To create a property for a driver, or to update an existing property, use one of the DDI driver update interfaces, such as ddi_prop_update_int(9F) or ddi_prop_update_string(9F), with the appropriate type for the property you want to create. (See Table 4–1 for a list of available property interfaces.) These interfaces are typically called from the driver's attach(9E) entry point. In this example, the attach() routine creates a string property called pm-hardware-state and gives it the value needs-suspend-resume:


     /* The following code is to tell cpr that this device
     * needs to be suspended and resumed.
     */
    (void) ddi_prop_update_string(device, dip,
         "pm-hardware-state", "needs-suspend-resume");

Looking up Properties

A driver can request a property from its parent, which in turn might ask its parent. The driver can control whether the request can go higher than its parent.

For example, the example driver below, “esp,” maintains an integer property for each target called targetx-sync-speed, where “x” is the target number. The prtconf(1M) command in its verbose mode displays driver properties. The following example shows a partial listing for the “esp” driver.

% prtconf -v
...
       esp, instance #0
            Driver software properties:
                name <target2-sync-speed> length <4>
                    value <0x00000fa0>.
...

The following table provides a summary of the property interfaces.

Table 4–1 Property Interface Uses

Family 

Property Interfaces 

Description 

ddi_prop_lookup 

ddi_prop_exists(9F)

Looks up a property and returns successfully if one exists. Fails if one does not exist  

 

ddi_prop_get_int(9F)

Looks up and returns an integer property 

 

ddi_prop_get_int64(9F)

Looks up and returns a 64–bit integer property 

 

ddi_prop_lookup_int_array(9F)

Looks up and returns an integer array property 

 

ddi_prop_lookup_int64_array(9F)

Looks up and returns a 64–bit integer array property 

 

ddi_prop_lookup_string(9F)

Looks up and returns a string property 

 

ddi_prop_lookup_string_array(9F)

Looks up and returns a string array property 

 

ddi_prop_lookup_byte_array(9F)

Looks up and returns a byte array property 

ddi_prop_update 

ddi_prop_update_int(9F)

Updates or creates an integer property 

 

ddi_prop_update_int64(9F)

Updates or creates a single 64–bit integer property 

 

ddi_prop_update_int_array(9F)

Updates or creates an integer array property 

 

ddi_prop_update_string(9F)

Updates or creates a string property 

 

ddi_prop_update_string_array(9F)

Updates or creates a string array property 

 

ddi_prop_update_int64_array(9F)

Updates or creates a 64–bit integer array property 

 

ddi_prop_update_byte_array(9F)

Updates or creates a byte array property 

ddi_prop_remove 

ddi_prop_remove(9F)

Removes a property 

 

ddi_prop_remove_all(9F)

Removes all properties associated with a device  

Whenever possible, use 64–bit versions of int property interfaces (such as ddi_prop_update_int64(9F)) instead of 32–bit versions (such as ddi_prop_update_int(9F)).

prop_op() Entry Point

ddi_prop_op(9F) can be used as a device driver's prop_op(9E) entry point when ddi_prop_op() is defined within the driver's cb_ops(9S) structure. ddi_prop_op() allows leaf devices to search for and obtain a property value from the device's property list.

It is sometimes useful for a device driver to define a driver-specific prop_op() routine within cb_ops rather than invoking ddi_prop_op(). For example, this is appropriate if a driver maintains a property whose value changes frequently. Changing such a value with ddi_prop_update() would not be efficient. In this case, the driver is responsible for updating the property value and should maintain a copy of the property value either within its soft state structure or in a driver variable.

The prop_op(9E) entry point reports the values of certain driver or device properties to the system. In many cases, the ddi_prop_op(9F) routine may be used as the driver's prop_op() entry point in the cb_ops(9S) structure. ddi_prop_op() performs all of the required processing and is sufficient for drivers that do not need to perform any special processing when handling a device property request.

However, there are cases when the driver must provide a prop_op() entry point. For example, if a driver maintains a property whose value changes frequently, updating the property with ddi_prop_update(9F) each time the value changes may not be efficient. Instead, the driver can maintain a local copy of the property in the instance's soft state. The driver updates the shadow copy in the soft state when the value of the property changes and does not call one of the ddi_prop_update() routines. In this case, the prop_op() entry point would need to intercept requests for this property and call one of the ddi_prop_update() routines to update the value of the property before passing the request to ddi_prop_op() to process the property request.

In Example 4–1, prop_op() intercepts requests for the temperature property. The driver updates a variable in the state structure whenever the property changes but only updates the property when a request is made. It then uses the system routine ddi_prop_op() to process the property request. If the property request is not specific to a device, the driver does not intercept the request. This is indicated when the value of the dev parameter is equal to DDI_DEV_T_ANY (the wildcard device number).


Example 4–1 prop_op(9E) Routine

static int
xxprop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
    int flags, char *name, caddr_t valuep, int *lengthp)
{
        minor_t instance;
        struct xxstate *xsp;
        if (dev != DDI_DEV_T_ANY) {
                return (ddi_prop_op(dev, dip, prop_op, flags, name,
                    valuep, lengthp));
        }

        instance = getminor(dev);
        xsp = ddi_get_soft_state(statep, instance);
        if (xsp == NULL)
                return (DDI_PROP_NOTFOUND);
        if (strcmp(name, "temperature") == 0) {
                ddi_prop_update_int(dev, dip, name, temperature);
        }

        /* other cases */    
}

Events

A system often needs to respond to an external state change in some device. For example, it may issue a warning when a component begins to overheat, or start a movie player when a DVD is inserted into a drive.

How Drivers Track Events: ddi_log_sysevent()

Device drivers use the ddi_log_sysevent(9F) interface to generate and log an event with the system; an event tells the system what has just occurred to the device. The system, in turn, queues events and passes them to the syseventd() daemon outside of kernel process space. syseventd() then passes the events on to an appropriate event-handling process, as shown in Figure 4–1.

Figure 4–1 Event Plumbing

Diagram shows how events are generated and logged.

ddi_log_sysevent() takes the following arguments:

dip

A pointer to the dev_info node for this driver.

vendor

A pointer to a string defining 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. This is a driver–specific value. An example of a class might be a string representing a set of environmental conditions affecting a device. This value must be intelligible to the event consumer.

subclass

Also a driver-specific string, this parameter represents a subset of the class argument. For example, within a class representing 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, listing name-value attributes associated with the event. Name-value attributes, like class and subclass, are driver-defined; they refer to some specific attribute or condition of the device. For example, a device that reads both CD-ROMs and DVDs may create a string name-value pair in which the name is disc_type and the value is either cd_rom or dvd. As with class and subclass, the driver handle (outside of kernel process space) must be able to interpret name–value pairs and respond to them in an appropriate manner.

For more on name-value pairs and the nvlist_t structure, see Event Name-Value Pairs, as well as nvlist_alloc(9F)

If there are no such attributes for an event, 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 timestamp. See the ddi_log_sysevent(9F) page for more information on the sysevent_id_t structure.

sleep_flag

This flag indicates how a caller will handle the possibility of resources not being available. If sleep_flag is DDI_NOSLEEP, then it does not matter if allocation fails or the queue is full; the caller will handle such a failure appropriately. If sleep_flag is set to DDI_SLEEP, the caller will have allocation and queuing routines wait for resources to become available.

The following example demonstrates how ddi_log_sysevent() is used.


char *vendor_name = "DDI_VENDOR_JGJG"
char *my_class = "EC_ENVIRONMENT";
char *my_subclass = "ESC_TEMPERATURE";
nvlist_t *nvl;
...
nvl = create_nvlist();
...
     /* an event occurs... */ ...
        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"); 

Event Name-Value Pairs

For interfaces such as ddi_log_sysevent(9F), the Solaris DDI provides a way to store 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 may be a boolean, an int (16, 32, and 64-bit, signed or unsigned), a string, or an array of ints, bytes, or strings.

The steps in creating a list of name-value pairs are as follows.

  1. Create an nvlist_t structure with nvlist_alloc(9F).

    The nvlist_alloc(9F) interface takes three arguments. The first is a pointer to an nvlist_t structure.

    The second is a flag relating to the uniqueness of the names of the pairs. If this flag is set to NV_UNIQUE_NAME_TYPE, any existing pair in the list will be removed if a new pair with the same name and type is added. If the flag is set to NV_UNIQUE_NAME, then any existing pair with the same name as one being added will be 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 neither value is used, then no uniqueness checking is done, and the consumer of the list must parse it in such a way as to ensure that any pair retrieved is the one wanted.

    The third argument relates to kernel memory allocation policy. If it is set to KM_SLEEP, then the driver will block until the requested memory is available for allocation. KM_SLEEP allocations may sleep but are guaranteed to succeed. KM_NOSLEEP allocations are guaranteed not to sleep but may fail (return NULL ) if no memory is currently available.

  2. 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).


Example 4–2 Creating and Populating a Name-Value Pair List

nvlist_t*
create_nvlist()
        {
        int err;
        char *str = "child";
        int32_t ints[] = {0, 1, 2};
        nvlist_t *nvl;
        err = nvlist_alloc(&nvl, 0);    /* allocate list */
        if (err)
                return (NULL);
        /* name="child" & prop={0, 1, 1} */
        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 its arguments the name and type of the pair being searched for. Note, however, that these interfaces will only work if either NV_UNIQUE_NAME or NV_UNIQUE_NAME_TYPE was specified when nvlist_alloc(9F) was called.

Name-value lists can be placed in contiguous memory (for example, to pass them to another process or send them to another host). To do so, first get the size of the memory block needed for the list with nvlist_size(9F), and then 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).

Kernel Statistics

A kstat or kernel statistic is a data structure that records the time, counters, various measurements, and other data regarding the use of a device. kstats are stored as NULL-terminated linked lists.

Table 4–2 kstat Member Descriptions

kstat Member 

Description 

ks_crtime 

Time at which the kstat was created, commonly used in calculating rates of various counters. 

ks_next 

Points to next kstat in chain. 

ks_kid 

Unique ID for kstat, for example,??? 

ks_module[KSTAT_STRLEN] 

Identifies kernel module that created kstat. ks_module is combined with ks_instance and ks_name to give the kstat a unique, meaningful name. KSTAT_STRLEN sets the maximum length of ks_module. 

ks_resv 

A reserved field. 

ks_instance 

The instance of the kernel module that created this kstat. ks_instance is combined with ks_module and ks_name to give the kstat a unique, meaningful name.  

ks_name[KSTAT_STRLEN] 

A name assigned to the kstat in combination with ks_module and ks_instance. KSTAT_STRLEN sets the maximum length of ks_module. 

ks_type 

The data type,which can be KSTAT_TYPE_RAW (binary data), KSTAT_TYPE_NAMED (name/value pairs), KSTAT_TYPE_INTR (interrupt statistics), KSTAT_TYPE_IO (I/O statistics), and KSTAT_TYPE_TIMER (event timers). 

ks_class[KSTAT_STRLEN] 

This categorizes the kstat into one of the following classes: bus, controller, device_error, disk, hat, kmem_cache, kstat, misc, net, nfs, pages, partition, rps, ufs, vm, and vmem. 

ks_data 

Points to the data saction for the kstat. 

ks_ndata 

Indicates the number of data records, for those kstat types that support multiple records (KSTAT_TYPE_RAW, KSTAT_TYPE_NAMED, and KSTAT_TYPE_TIMER). 

ks_data_size 

Total size of the data section in bytes. 

ks_snaptime 

The timestamp for the last data sanpshot, useful in calculating rates.