Part I Designing Device Drivers for the Solaris Platform
1. Overview of Solaris Device Drivers
2. Solaris Kernel and Device Tree
5. Managing Events and Queueing Tasks
7. Device Access: Programmed I/O
10. Mapping Device and Kernel Memory
14. Layered Driver Interface (LDI)
Part II Designing Specific Kinds of Device Drivers
15. Drivers for Character Devices
Overview of the Character Driver Structure
Character Device Autoconfiguration
Device Access (Character Drivers)
open() Entry Point (Character Drivers)
close() Entry Point (Character Drivers)
Differences Between Synchronous and Asynchronous I/O
Multiplexing I/O on File Descriptors
ioctl() Entry Point (Character Drivers)
I/O Control Support for 64-Bit Capable Device Drivers
32-bit and 64-bit Data Structure Macros
How Do the Structure Macros Work?
Declaring and Initializing Structure Handles
18. SCSI Host Bus Adapter Drivers
19. Drivers for Network Devices
Part III Building a Device Driver
21. Compiling, Loading, Packaging, and Testing Drivers
22. Debugging, Testing, and Tuning Device Drivers
23. Recommended Coding Practices
B. Summary of Solaris DDI/DKI Services
C. Making a Device Driver 64-Bit Ready
The method in Example 15-15 works well for many drivers. An alternate scheme is to use the data structure macros that are provided in <sys/model.h>to move data between the application and the kernel. These macros make the code less cluttered and behave identically, from a functional perspective.
Example 15-16 Using Data Structure Macros to Move Data
int xxioctl(dev_t dev, int cmd, intptr_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); xxdowork(device_state, STRUCT_FGET(op, size)); return (0); }
In a 64-bit device driver, structure macros enable the use of the same piece of kernel memory by data structures of both sizes. The memory buffer holds the contents of the native form of the data structure, that is, the LP64 form, and the ILP32 form. Each structure access is implemented by a conditional expression. When compiled as a 32-bit driver, only one data model, the native form, is supported. No conditional expression is used.
The 64-bit versions of the macros depend on the definition of a shadow version of the data structure. The shadow version describes the 32-bit interface with fixed-width types. The name of the shadow data structure is formed by appending “32” to the name of the native data structure. For convenience, place the definition of the shadow structure in the same file as the native structure to ease future maintenance costs.
The macros can take the following arguments:
The structure name of the native form of the data structure as entered after the struct keyword.
A flag word that contains the user data model, such as FILP32 or FLP64, extracted from the mode parameter of ioctl(9E).
The name used to refer to a particular instance of a structure that is manipulated by these macros.
The name of the field within the structure.
Macros enable you to make in-place references only to the fields of a data item. Macros do not provide a way to take separate code paths that are based on the data model. Macros should be avoided if the number of fields in the data structure is large. Macros should also be avoided if the frequency of references to these fields is high.
Macros hide many of the differences between data models in the implementation of the macros. As a result, 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, but still preserves type checking.
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 - Because the STRUCT_DECL(9F) and STRUCT_HANDLE(9F) macros expand to data structure declarations, these macros should be grouped with such declarations in C code.
The macros for declaring and initializing structures are as follows:
Declares a structure handlethat is called handle for a structname data structure. STRUCT_DECL allocates space for its native form on the stack. The native form is assumed to be larger than or equal to the ILP32 form of the structure.
Initializes the data model for handle to umodel. This macro must be invoked before any access is made to a structure handle declared with STRUCT_DECL(9F).
Declares a structure handle that is called handle. Contrast with STRUCT_DECL(9F).
Initializes the data model for handle to umodel, and sets addr as the buffer used for subsequent manipulation. Invoke this macro before accessing a structure handle declared with STRUCT_DECL(9F).
The macros for performing operations on structures are as follows:
Returns the size of the structure referred to by handle, according to its embedded data model.
Returns the indicated field in the data structure referred to by handle. This field is a non-pointer type.
Returns the indicated field in the data structure referred to by handle. This field is a pointer type.
Sets the indicated field in the data structure referred to by handle to value val. The type of val should match the type of fieldname. The field is a non-pointer type.
Sets the indicated field in the data structure referred to by handle to value val. The field is a pointer type.
Returns the address of the indicated field in the data structure referred to by handle.
Returns a pointer to the native structure described by handle.
Some miscellaneous structure macros follow:
Returns the size of struct_name, which is based on the given data model.
Returns the size of a pointer based on the given data model.