Writing Device Drivers


The system calls attach(9E) to attach a device instance to the system or to resume operation after power has been suspended. attach(9E) should handle the following commands:

Only the DDI_ATTACH command is discussed in this section. For information on DDI_PM_RESUME and DDI_RESUME, see Chapter 8, Power Management.

Note that attach(9E) is single-threaded when processing the DDI_ATTACH command, but is not single-threaded when processing the DDI_RESUME or DDI_PM_RESUME commands.

The responsibilities of the DDI_ATTACH case of attach(9E) include:

Example 5-4 attach(9E) Routine

static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
	struct xxstate *xsp;
	int	instance;

	/* define device access attributes */
	ddi_device_acc_attr_t access_attr = {
	switch (cmd) {

		/* get assigned instance number */
		instance = ddi_get_instance(dip);
		if (ddi_soft_state_zalloc(statep, instance) != 0)
			return (DDI_FAILURE);
		xsp = ddi_get_soft_state(statep, instance);

		/* retrieve interrupt block cookie */
		if (ddi_get_iblock_cookie(dip, inumber,
				&xsp->iblock_cookie) != DDI_SUCCESS) {
			ddi_soft_state_free(statep, instance);
			return (DDI_FAILURE);
		/* initialize locks. Note that mutex_init wants a */
 	/* ddi_iblock_cookie, not the _address_ of one, */
		/* as the fourth argument.*/
		mutex_init(&xsp->mu, "xx mutex", MUTEX_DRIVER,
			(void *)xsp->iblock_cookie);
		cv_init(&xsp->cv, "xx cv", CV_DRIVER, NULL);

		/* set up interrupt handler for the device */
		if (ddi_add_intr(dip, inumber, NULL,
			&xsp->idevice_cookie, NULL, intr_handler,intr_handler_arg)
			!= DDI_SUCCESS) {
			ddi_soft_state_free(statep, instance);
			return (DDI_FAILURE);
		/* map device registers */
		if (ddi_regs_map_setup(dip, rnumber, &xsp->regp, offset, 
			sizeof(struct device_reg), &access_attr,
			&xsp->data_access_handle) != DDI_SUCCESS) {
			ddi_remove_intr(dip, inumber, xsp->iblock_cookie);
			ddi_soft_state_free(statep, instance);
			return (DDI_FAILURE);
		xsp->dip = dip;
   initialize the rest of the software state structure;
		make device quiescent;				/* device-specific */

		 * for devices with programmable bus interrupt level
		program device interrupt level using xsp->idevice_cookie;
		if device has power manageable components, then include the following statement:
		if (pm_create_components(dip, num_components) != DDI_SUCCESS)
			goto failed;
		for (i = 0; i< num_components; i++) {
			if (pm_idle_component(dip, i) == DDI_FAILURE)
				goto failed;
		/* If the driver manages devices with "remote" hardware,
		 * suspend/resume will not be called unless requested, by
		 * setting the "pm_hardware_state" property to the value
		 * "needs_suspend_resume".
		if (ddi_prop_update_string (ddi_dev_t_none, dip, 
			"pm_hardware_state", "needs_suspend_resume") != DDI_PROP_SUCCESS) {
			goto failed;
		if (ddi_create_minor_node(dip, "minor name", S_IFCHR,
			minor_number, node_type, 0) != DDI_SUCCESS)
			goto failed;
		initialize driver data, prepare for a later open of the device; 
   /*device-specific */
		return (DDI_SUCCESS);

		For information, see Chapter 8, Power Management
		For information, see Chapter 8, Power Management	
		return (DDI_FAILURE);
	free allocated resources
	ddi_remove_intr(dip, inumber, xsp->iblock_cookie);
	ddi_soft_state_free(statep, instance);
	return (DDI_FAILURE);

During the autoconfiguration process, attach(9E) checks for the DDI_ATTACH command and then calls ddi_get_instance(9F) to get the instance number the system has assigned to the dev_info node indicated by dip. Since the driver must be able to return a pointer to its dev_info node for each instance, attach(9E) must save dip, usually in a field of a per-instance state structure.

If any of the resource allocation routines fail, the code at the failed label should free any resources that had already been allocated before returning DDI_FAILURE. This can be done with a series of checks that look like this:

	if (xsp->regp)

There should be such a check and a deallocation operation for each allocation operation that may have been performed.

Note also that drivers should return DDI_FAILURE for all commands they do not recognize.

Registering Interrupts Overview

In the call to ddi_add_intr(9F), inumber specifies which of several possible interrupt specifications is to be handled by intr_handler. For example, if the device interrupts at only one level, pass 0 for inumber. The interrupt specifications being referred to by inumber are described by the interrupts property (see driver.conf(4), isa(4), eisa(4), mca(4), sysbus(4), vme(4), and sbus(4)). intr_handler is a pointer to a function, in this case xxintr()(), to be called when the device issues the specified interrupt. intr_handler_arg is an argument of type caddr_t to be passed to intr_handler. intr_handler_arg may be a pointer to a data structure representing the device instance that issued the interrupt. ddi_add_intr(9F) returns a device cookie in xsp->idevice_cookie for use with devices having programmable bus-interrupt levels. The device cookie contains the following fields:

	u_short			idev_vector;
 	u_short			idev_priority;

The idev_priority field of the returned structure contains the bus interrupt priority level, and the idev_vector field contains the vector number for vectored bus architectures such as VMEbus.

Note -

There is a potential race condition in attach(9E). The interrupt routine is eligible to be called as soon as ddi_add_intr(9F) returns. This may result in the interrupt routine being called before any mutexes have been initialized with the interrupt block cookie. If the interrupt routine acquires the mutex before it has been initialized, undefined behavior may result. See "Registering Interrupts" for a solution to this problem.

Mapping Device Registers

In the ddi_regs_map_setup(9F) call, dip is the dev_info pointer passed to attach(9E). rnumber specifies which register set to map if there is more than one. For devices with only one register set, pass 0 for rnumber. The register specifications referred to by rnumber are described by the reg property (see driver.conf(4), isa(4), eisa(4), mca(4), sysbus(4), vme(4), sbus(4), and pci(4)). ddi_regs_map_setup(9F) maps a device register set (register specification) and returns a bus address base in xsp->regp. This address is offset bytes from the base of the device register set, and the mapping extends sizeof(struct device_reg) bytes beyond that. To map all of a register set, pass zero for offset and the length.

Minor Device Nodes

A minor device node contains the information exported by the device that the system uses to create a special file for the device under /devices in the file system.

In the call to ddi_create_minor_node(9F), the minor name is the character string that is the last part of the base name of the special file to be created for this minor device number; for example, "b,raw" in "fd@1,f7200000:b,raw". S_IFCHR means create a character special file. Finally, the node type is one of the following system macros, or any string constant that does not conflict with the values of these macros (see ddi_create_minor_node(9F) for more information).

Table 5-1 Possible Node Types




Serial port 


Dialout ports 


Hard disks 


Hard disks with channel or target numbers 


ROM drives (CD-ROM) 


ROM drives with channel or target numbers 


Floppy disks 


Tape drives 


Network devices 


Display devices 






General pseudo devices 

The node types DDI_NT_BLOCK, DDI_NT_BLOCK_CHAN, DDI_NT_CD, and DDI_NT_CD_CHAN cause disks(1M) to identify the device instance as a disk and to create a symbolic link in the /dev/dsk or /dev/rdsk directory pointing to the device node in the /devices directory tree.

The node type DDI_NT_TAPE causes tapes(1M) to identify the device instance as a tape and to create a symbolic link from the /dev/rmt directory to the device node in the /devices directory tree.

The node type DDI_NT_SERIAL causes ports(1M) to identify the device instance as a serial port and to create symbolic links from the /dev/term and /dev/cua directories to the device node in the /devices directory tree and to add a new entry to /etc/inittab.

Vendor-supplied strings should include an identifying value to make them unique, such as their name or stock symbol (if appropriate). The string (along with the other node types not consumed by disks(1M), tapes(1M), or ports(1M)) can be used in conjunction with devlinks(1M) and devlink.tab(4) to create logical names in /dev.

Deferred Attach

open(9E) might be called before attach(9E) has succeeded. open(9E) must then return ENXIO, which will cause the system to attempt to attach the device. If the attach succeeds, the open is retried automatically.