Making a Device Driver 64-Bit Ready
This appendix provides information for device driver writers who are converting their device drivers to support the 64-bit kernel. It presents the differences between 32-bit and 64-bit device drivers and describes the steps to convert 32-bit device drivers to 64-bit. This information is specific to regular character and block device drivers only.
This appendix provides information on the following subjects:
Introduction to 64-Bit Driver Design
For drivers that only need support for the 32-bit kernel, existing 32-bit device drivers will continue to work without recompilation. However, most device drivers require some changes to run correctly in the 64-bit kernel, and all device drivers require recompilation to create a 64-bit driver module. The information in this appendix will help you to enable drivers for 32-bit and 64-bit environments to be generated from common source code, thus increasing code portability and reducing the maintenance effort.
Before starting to clean up a device driver for the 64-bit environment, you should understand how the 32-bit environment differs from the 64-bit environment. In particular, you must be familiar with the C language data type models ILP32 and LP64, and to be aware of driver-specific issues. Driver-specific issues are the subject of this appendix, while more general issues are covered extensively in Solaris 64-bit Developer's Guide.
In addition to general code cleanup to support the data model changes for LP64, driver writers have to provide support for both 32-bit and 64-bit applications.
The ioctl(9E), devmap(9E), and mmap(9E) entry points enable data structures to be shared directly between applications and device drivers. If those data structures change size between the 32-bit and 64-bit environments, then the entry points must be modified so that the driver can determine whether the data model of the application is the same as that of the kernel. When the data models differ, data structures can be adjusted. See "I/O Control Support for 64-Bit Capable Device Drivers", "32-bit and 64-bit Data Structure Macros", and "Associating Kernel Memory With User Mappings".
Practically speaking, in many drivers, only a few ioctls need this kind of handling; the others will work without change as long as they pass around data structures that do not change in size.
General Conversion Steps
The sections below provide information on converting drivers to run in a 64-bit environment. Driver writers might need to do one or more of the following:
Use fixed-width types for hardware registers.
Use fixed-width common access functions.
Check and extend use of derived types.
Check changed fields within DDI data structures.
Check changed arguments of DDI functions.
Modify the driver entry points that handle user data, where needed.
These steps are explained in detail below.
After each step is complete, fix all compiler warnings, and use lint to look for other problems. The SC5.0 (or newer) version of lint should be used with -Xarch=v9 and -errchk=longptr64 specified to find 64-bit problems. See the notes on using and interpreting the output of lint in the Solaris 64-bit Developer's Guide.
Caution - Do not ignore compilation warnings during conversion for LP64. Even those that were safe to ignore previously in the ILP32 environment might now indicate a more serious problem.
After all the steps are complete, compile and test the driver as both a 32-bit and 64-bit modules.
Use Fixed-width Types for Hardware Registers
Many device drivers that manipulate hardware devices use C data structures to describe the layout of the hardware. In the LP64 data model, data structures that use long or unsigned long to define hardware registers are almost certainly incorrect, because long is now a 64-bit quantity. Start by including <sys/inttypes.h>, and update this class of data structure to use int32_t or uint32_t instead of long for 32-bit device data. This preserves the binary layout of 32-bit data structures. For example, change:
struct device_regs { ulong_t addr; uint_t count; }; /* Only works for ILP32 compilation */ |
to:
struct device_regs { uint32_t addr; uint32_t count; }; /* Works for any data model */ |
Use Fixed-width Common Access Functions
The Solaris DDI permits device registers to be accessed by access functions for portability to multiple platforms. Previously, the DDI common access functions specified the size of data in terms of bytes, words, and so on. For example, ddi_getl(9F) is used to access 32-bit quantities. This function is not available in the 64-bit DDI environment, and has been replaced by versions that are specified using the number of bits that they manipulate.
These routines were added to the 32-bit kernel in the Solaris 2.6 operating environment, to permit their early adoption by driver writers. For example, to be portable to both 32-bit and 64-bit kernels, the driver must use ddi_get32(9F) to access 32-bit data rather than ddi_getl(9F).
The entire set of common access routines is replaced by their fixed-width equivalents. See the ddi_get8(9F), ddi_put8(9F), ddi_rep_get8(9F), and ddi_rep_put8(9F) man pages for details.
Check and Extend Use of Derived Types
System-derived types, such as size_t, should be used where possible so that the resulting variables make sense when passed between functions. The new derived types uintptr_t or intptr_t should be used as the integral type for pointers.
Fixed-width integer types are useful for representing explicit sizes of binary data structures or hardware registers, while fundamental C language data types, such as int, can still be used for loop counters or file descriptors.
Some system-derived types represent 32-bit quantities on a 32-bit system but represent 64-bit quantities on a 64-bit system. Derived types that change size in this way include: clock_t, daddr_t, dev_t, ino_t, intptr_t, off_t, size_t, ssize_t, time_t, uintptr_t, and timeout_id_t.
Drivers that use these derived types should pay particular attention to their use, particularly if they are assigning these values to variables of another derived type, such as a fixed-width type.
Check Changed Fields in DDI Data Structures
The data types of some of the fields within DDI data structures, such as buf(9S), have been changed. Drivers that use these data structures should make sure that these fields are being used appropriately. The data structures and the fields that were changed in a significant way are listed below.
buf Structure Changes
size_t b_bcount; /* was type unsigned int */ size_t b_resid; /* was type unsigned int */ size_t b_bufsize; /* was type long */ |
The fields changed here pertain to transfer size, which can now exceed more than 4 Gbytes.
ddi_dma_attr
The ddi_dma_attr(9S) structure defines attributes of the DMA engine and the device. Because these attributes specify register sizes, fixed-width data types have been used instead of fundamental types.
ddi_dma_cookie Structure Changes
uint32_t dmac_address; /* was type unsigned long */ size_t dmac_size; /* was type u_int */ |
The ddi_dma_cookie(9S) structure contains a 32-bit DMA address, so a fixed-width data type has been used to define it. The size has been redefined as size_t.
csi_arq_status Structure Changes
uint_t sts_rqpkt_state; /* was type u_long */ uint_t sts_rqpkt_statistics; /* was type u_long */ |
These fields in the structure do not need to grow and have been redefined as 32-bit quantities.
scsi_pkt Structure Changes
uint_t pkt_flags; /* was type u_long */ int pkt_time; /* was type long */ ssize_t pkt_resid; /* was type long */ uint_t pkt_state; /* was type u_long */ uint_t pkt_statistics; /* was type u_long */ |
Because the pkt_flags, pkt_state, and pkt_statistics fields in the scsi_pkt(9S) structure do not need to grow, they have been redefined as 32-bit integers. The data transfer size pkt_resid field does grow and has been redefined as ssize_t.