Writing Device Drivers

attach()

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 = {
	    	DDI_DEVICE_ATTR_V0,
	    	DDI_STRUCTURE_BE_ACC,
	    	DDI_STRICTORDER_ACC
	};
	switch (cmd) {
	case DDI_ATTACH:

		/* 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 */
		ddi_report_dev(dip);
		return (DDI_SUCCESS);

	case DDI_PM_RESUME:
		For information, see Chapter 8, Power Management
	case DDI_RESUME:
		For information, see Chapter 8, Power Management	
   default:
		return (DDI_FAILURE);
	}
failed:
	free allocated resources
	ddi_regs_map_free(&xsp->data_access_handle);
	ddi_remove_intr(dip, inumber, xsp->iblock_cookie);
	pm_destroy_components(dip);
	cv_destroy(&xsp->cv);
	mutex_destroy(&xsp->mu);
	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)
 		ddi_regs_map_free(&xsp->data_access_handle);

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

Constant 

Description 

DDI_NT_SERIAL

Serial port 

DDI_NT_SERIAL_DO

Dialout ports 

DDI_NT_BLOCK

Hard disks 

DDI_NT_BLOCK_CHAN

Hard disks with channel or target numbers 

DDI_NT_CD

ROM drives (CD-ROM) 

DDI_NT_CD_CHAN

ROM drives with channel or target numbers 

DDI_NT_FD

Floppy disks 

DDI_NT_TAPE

Tape drives 

DDI_NT_NET

Network devices 

DDI_NT_DISPLAY

Display devices 

DDI_NT_MOUSE

Mouse 

DDI_NT_KEYBOARD

Keyboard 

DDI_PSEUDO

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.