Writing Device Drivers

Data Structure Macros

There are two ways to write the ioctl handler of a driver to deal with the possible differences between the data model of the the user application. This section presents one method, using a new set of macros when referring to the data structure from the user application.

While the method shown in Example F-1 will work well for many drivers, this alternate scheme is to use the data structure macros provided in <sys/model.h> to move data between the application and the kernel. These tend to make the code less cluttered, and behave identically from a functional perspective. Here's the first example recoded using the data structures macros.


Example F-4 ioctl(9E)

int
xxioctl(dev_t dev, int cmd, intprt_t arg, int mode,
				cred_t *cr, int *rval_p)
{
		STRUCT_DECL(opdata, op);

		if (cmd != OPONE)
			return (ENOTTY);

		STRUCT_INIT(op, mode);

		if (copyin((void *)arg,
			STRUCT_BUF(op), STRUCT)SIZE(op)))
				return (EFAULT);

		if (STRUCT_FGET(op,flag) != XXACTIVE ||
			STRUCT_FGET(op, size) > XXSIZE)
				return (EINVAL);

		xxdwork(device_state, STRUCT_FGET(op, size));
		return (O);
	}

How Structure Macros Work

In a 64-bit device driver, these macros do all that is necessary to use the same piece of kernel memory as a buffer for the contents of the native form of the data structure (that is, the LP64 form), and for the ILP32 form of the same structure. This usually means that each structure access is implemented by a conditional expression. When compiled as a 32-bit driver, since there is only one supported data model, there is only native form, so no conditional expression is used.

The 64-bit versions of the macros depend on the definition of a shadow version of the data structure which describes the 32-bit interface using fixed-width types. The name of the shadow data structure is formed by appending "32" to the name of the native data structure. It is convenient to place the definition of the shadow structure in the same file as the native structure to ease future maintenance costs.

The macros take arguments such as:

structname

The structure name (as would appear after the struct keyword) of the native form of the data structure

umodel

A flag word containing the user data model, such as FILP32 or FLP64, extracted from the mode parameter of ioctl(9E)

handle

The name used to refer to a particular instance of a structure which is manipulated by these macros

fieldname

The name of the field within the structure

Declaring and Initializing Structure Handles

STRUCT_DECL(9F) and STRUCT_INIT(9F) can be used to declare and initialize a handle and space for decoding an ioctl on the stack. STRUCT_HANDLE(9F) and STRUCT_SET_HANDLE(9F) declare and initialize a handle without allocating space on the stack. The latter macros can be useful if the structure is very large, or is contained in some other data structure.


Note -

Since the STRUCT_DECL() and STRUCT_HANDLE() macros expand to data structure declarations, they should be grouped with such declarations in C code.


STRUCT_DECL(structname, handle)

Declares a structure handle called handle for a struct structname data structure, and allocated space for its native form on the stack. It is assumed that the native form is larger than or equal to the ILP32 form of the structure.

STRUCT_INIT(handle, umodel)

Initializes the date model for handle to umodel. This macro must be invoked before any access is made to a structure handle declared with STRUCT_DECL(9F).

STRUCT_HANDLE(structname, handle)

Declares a structure handle called handle. Contrast with STRUCT_DECL(9F).

STRUCT_SET_HANDLE(handle, umodel, addr)

Initializes the data model for handle to umodel, and set addr as the buffer used for subsequent manipulation. This macro must be invoked before any access is made to a structure handle declared with STRUCT_HANDLE(9F).

Operations on Structure Handles

size_t STRUCT_SIZE(handle)

Returns the size of the structure referred to by handle according to its embedded data model.

typeof fieldname STRUCT_FGET(handle, fieldname)

Returns the indicated field (non-pointer type) in the data structure referred to by handle.

typeof fieldname STRUCT_FGETP(handle, fieldname)

Returns the indicated field (pointer type) in the data structure referred to by handle.

STRUCT_FSET(handle, fieldname, val)

Sets the indicated field (non-pointer type) in the data structure referred to by handle to value val. The type of val should match the type of fieldname.

STRUCT_FSETP(handle, fieldname, val)

Sets the indicated field (pointer type) in the data structure referred to by handle to value val.

typeof fieldname *STRUCT_FADDR(handle, fieldname)

Returns the address of the indicated field in the data structure referred to by handle.

struct structname *STRUCT_BUF(handle)

Returns a pointer to the native structure described by handle.

Other Operations

size_t SIZEOF_STRUCT(structname, datamodel)

Returns the size of struct_name based on the given data model.

size_t SIZEOF_PTR(datamodel)

Returns the size of a pointer based on the given data model.

When to Use Structure Macros

Macros only allow you to make in-place references to the fields of a data item. They do not provide a way to take separate code paths based on the data model. They should be avoided if the number of fields in the data structure is large or the frequency of references to these fields is high.

Because macros hide many of the differences between data models in the implementation of the macros, code written with this interface is generally easier to read.

When compiled as a 32-bit driver, the resulting code is compact without needing clumsy #ifdefs, while preserving type checking.

Macros are best suited for making in-place references to the fields of a data structure, particularly if the number of fields in the data structure is small and the frequency of references to these fields is low.