The sections below provide information on converting drivers to run in a 64-bit environment. Driver writers might need to perform one or more of the following tasks:
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.
Check structures that use 64-bit long types on x86 platforms.
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.
Do not ignore compilation warnings during conversion for LP64. Warnings 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 module.
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 approach 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 */
The Oracle Solaris DDI allows 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 of the function that specify the number of bits to be acted on.
These routines were added to the 32-bit kernel in the Solaris 2.6 operating environment, to enable 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).
All common access routines are 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.
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
.
When designing drivers that use these derived types, pay particular attention to the use of these types, particularly if the drivers are assigning these values to variables of another derived type, such as a fixed-width type.
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.
The fields listed below pertain to transfer size, which can now exceed more than 4 Gbytes.
size_t b_bcount; /* was type unsigned int */ size_t b_resid; /* was type unsigned int */ size_t b_bufsize; /* was type long */
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.
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 the address. The size has been redefined
as size_t
.
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.
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, these fields have been redefined as 32-bit integers.
The data transfer size pkt_resid field does grow
and has been redefined as ssize_t
.
This section describes the DDI function argument data types that have been changed.
struct buf *getrbuf(int sleepflag);
In previous releases, sleepflag was defined as a type long
.
int drv_getparm(unsigned int parm, void *value_p);
In previous releases, value_p was defined as type unsigned long
. In the 64-bit kernel, drv_getparm(9F) can fetch both 32-bit and 64-bit quantities. The
interface does not define data types of these quantities, and simple programming
errors can occur.
The following new routines offer a safer alternative:
clock_t ddi_get_lbolt(void); time_t ddi_get_time(void); cred_t *ddi_get_cred(void); pid_t ddi_get_pid(void);
Driver writers are strongly urged to use these routines instead of drv_getparm(9F).
void delay(clock_t ticks); timeout_id_t timeout(void (*func)(void *), void *arg, clock_t ticks);
The ticks argument to the delay(9F) and timeout(9F) routines
has been changed from long
to clock_t
.
struct map *rmallocmap(size_t mapsize); struct map *rmallocmap_wait(size_t mapsize);
The mapsize argument
to the rmallocmap(9F) and rmallocmap_wait(9F) routines has been changed
from ulong_t
to size_t
.
struct buf *scsi_alloc_consistent_buf(struct scsi_address *ap, struct buf *bp, size_t datalen, uint_t bflags, int (*callback )(caddr_t), caddr_t arg);
In previous releases, datalen was defined as an int
and bflags was
defined as a ulong
.
int uiomove(caddr_t address, size_t nbytes, enum uio_rw rwflag, uio_t *uio_p);
The nbytes argument was defined
as a type long
, but because nbytes represents
a size in bytes, size_t
is more appropriate.
int cv_timedwait(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout); int cv_timedwait_sig(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout);
In previous releases, the timeout argument to
the cv_timedwait(9F) and cv_timedwait_sig(9F) routines was defined to be
of type long
. Because these routines represent time in ticks, clock_t
is more appropriate.
int ddi_device_copy(ddi_acc_handle_t src_handle, caddr_t src_addr, ssize_t src_advcnt, ddi_acc_handle_t dest_handle, caddr_t dest_addr, ssize_t dest_advcnt, size_t bytecount, uint_t dev_datasz);
The src_advcnt, dest_advcnt, dev_datasz arguments have changed type. These arguments were previously
defined as long
, long
, and ulong_t
respectively.
int ddi_device_zero(ddi_acc_handle_t handle, caddr_t dev_addr, size_t bytecount, ssize_t dev_advcnt, uint_t dev_datasz):
In previous releases, dev_advcnt was defined
as a type long
and dev_datasz as a ulong_t
.
int ddi_dma_mem_alloc(ddi_dma_handle_t handle, size_t length, ddi_device_acc_attr_t *accattrp, uint_t flags, int (*waitfp)(caddr_t), caddr_t arg, caddr_t *kaddrp, size_t *real_length, ddi_acc_handle_t *handlep);
In previous releases, length, flags, and real_length were defined with types uint_t
, ulong_t
, and uint_t *
.
If a device driver shares data structures
that contain long
s or pointers with a 32-bit application using ioctl(9E), devmap(9E), or mmap(9E), and the driver is recompiled
for a 64-bit kernel, the binary layout of data structures will be incompatible.
If a field is currently defined in terms of type long
and 64-bit
data items are not used, change the data structure to use data types that
remain as 32-bit quantities (int
and unsigned
int
). Otherwise, the driver needs to be aware of the different structure
shapes for ILP32 and LP64 and determine whether a model mismatch between the
application and the kernel has occurred.
To handle potential data model differences, the ioctl(), devmap(), and mmap() driver entry points, which interact directly with user applications, need to be written to determine whether the argument came from an application using the same data model as the kernel.
To determine whether a model mismatch exists between the application and the driver, the driver uses the FMODELS mask to determine the model type from the ioctl() mode argument. The following values are OR-ed into mode to identify the application data model:
The code examples in I/O Control Support for 64-Bit Capable Device Drivers show how this situation can be handled using ddi_model_convert_from(9F).
To enable a 64-bit driver and a 32-bit application to share memory, the binary layout generated by the 64-bit driver must be the same as the layout consumed by the 32-bit application. The mapped memory being exported to the application might need to contain data-model-dependent data structures.
Few memory-mapped devices face this problem because the device registers do not change size when the kernel data model changes. However, some pseudo-devices that export mappings to the user address space might want to export different data structures to ILP32 or LP64 applications. To determine whether a data model mismatch has occurred, devmap(9E) uses the model parameter to describe the data model expected by the application. The model parameter is set to one of the following values:
The model parameter can be passed untranslated to the ddi_model_convert_from(9F) routine or to STRUCT_INIT(). See 32-bit and 64-bit Data Structure Macros.
Because mmap(9E) does not have a parameter that can be used to pass data model information, the driver's mmap(9E) entry point can be written to use the new DDI function ddi_model_convert_from(9F). This function returns one of the following values to indicate the application's data type model:
DDI_FAILURE – Function was not called from mmap(9E)
As with ioctl() and devmap(), the model bits can be passed to ddi_model_convert_from(9F) to determine whether data conversion is necessary, or the model can be handed to STRUCT_INIT().
Alternatively, migrate the device driver to support the devmap(9E) entry point.
You should carefully check structures that use 64-bit long types, such as uint64_t, on the x86 platforms. The alignment and size can differ between compilation in 32-bit mode versus a 64-bit mode. Consider the following example.
#include <studio> #include <sys> struct myTestStructure { uint32_t my1stInteger; uint64_t my2ndInteger; }; main() { struct myTestStructure a; printf("sizeof myTestStructure is: %d\n", sizeof(a)); printf("offset to my2ndInteger is: %d\n", (uintptr_t)&a.bar - (uintptr_t)&a); }
On a 32-bit system, this example displays the following results:
sizeof myTestStructure is: 12 offset to my2ndInteger is: 4 |
Conversely, on a 64-bit system, this example displays the following results:
sizeof myTestStructure is: 16 offset to my2ndInteger is: 8 |
Thus, the 32-bit application and the 64-bit application view the structure differently. As a result, trying to make the same structure work in both a 32-bit and 64-bit environment can cause problems. This situation occurs often, particularly in situations where structures are passed into and out of the kernel through ioctl() calls.