Writing Device Drivers

ioctl(9E)

int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode,
 	cred_t *credp, int *rvalp);

The cmd parameter indicates which command ioctl(9E) should perform. By convention, I/O control commands indicate the driver they belong to in bits 8-15 of the command (usually given by the ASCII code of a character representing the driver), and the driver-specific command in bits 0-7. They are usually created in the following way:

	#define XXIOC		(`x' << 8)		/* `x' is a character representing */
 											/* device xx */
	#define XX_GET_STATUS				(XXIOC | 1) /* get status register */
 #define XX_SET_CMD					(XXIOC | 2) /* send command */

The interpretation of arg depends on the command. I/O control commands should be documented (in the driver documentation, or a manual page) and defined in a public header file, so that applications can determine the names, what they do, and what they accept or return as arg. Any data transfer of arg (into or out of the driver) must be performed by the driver.

Drivers must use ddi_copyin(9F) to transfer "arg" data from the userland application to the kernel and ddi_copyout(9F) from kernel to userland. Failure to use ddi_copyin(9F) or ddi_copyout(9F) will result in panics on architectures that separate kernel and user address spaces, or if the user address has been swapped out.

ioctl(9E) is usually a switch statement with a case for each supported ioctl(9E) request.


Example 9-13 ioctl(9E) Routine

static int
xxioctl(dev_t dev, int cmd, intptr_t arg, int mode,
	cred_t *credp, int *rvalp)
{
 	uint8_t				csr;
 	struct xxstate 		*xsp;

 	xsp = ddi_get_soft_state(statep, getminor(dev));
 	if (xsp == NULL) {
	    	return (ENXIO);
 	}
 	switch (cmd) {
 	case XX_GET_STATUS:
		   csr = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
	   	if (ddi_copyout(&csr, (void *)arg,
				      sizeof (uint8_t), mode) != 0) {
			   return (EFAULT);
		   }
	   	break;
 	case XX_SET_CMD:
	     if (ddi_copyin((void *)arg, &csr,
				      sizeof (uint8_t), mode) != 0) {
			   return (EFAULT);
	   	}
	   	ddi_put8(xsp->data_access_handle, &xsp->regp->csr, csr);
	    	break;
 	default:
	   	/* generic "ioctl unknown" error */
	   	return (ENOTTY);
 	}
 	return (0);
}

The cmd variable identifies a specific device control operation. If arg contains a user virtual address, ioctl(9E) must call ddi_copyin(9F) or ddi_copyout(9F) to transfer data between the data structure in the application program pointed to by arg and the driver. In Example 9-13, for the case of an XX_GET_STATUS request the contents of xsp->regp->csr are copied to the address in arg. When a request succeeds, ioctl(9E) can store in *rvalp any integer value to be the return value of the ioctl(2) system call that made the request. Negative return values, such as -1, should be avoided, as they usually indicate the system call failed, and many application programs assume that negative values indicate failure.

An application that uses the I/O controls discussed above could look like Example 9-14.


Example 9-14 Using ioctl(9E)

#include <sys/types.h>
#include "xxio.h"				/* contains device's ioctl cmds and arguments */
int
main(void)
{
 	uint8_t		status;
 	...

 	/*
	    * read the device status
	    */
 	if (ioctl(fd, XX_GET_STATUS, &status) == -1) {
	    	 error handling
 	}
 	printf("device status %x\n", status);
 	exit(0);
}