The sections below provide information on converting drivers to run in a 64-bit environment. Driver writers may need to do one or more of the following:
Convert driver code to be LP64 clean
Update driver data structures to preserve fixed-width data in register layouts
Check use of derived types that change size in ILP32 and LP64
Use new fixed-width DDI common access functions
Modify the driver entry points that handle data sharing
Check changed fields in DDI data structures
Check changed arguments of DDI interfaces
Convert well-known ioctl interfaces
As a first step in converting a device driver to run in a 64-bit kernel, make the driver 64-bit clean by converting to the LP64 data model. For example, make sure that the driver's use of long variables remains consistent between data models.
To enable source code to be both 32-bit and 64-bit safe, the Solaris 2.6 system provides new fixed-width integer types, derived types, constants, and macros in the files <sys/types.h> and <sys/inttypes.h>. The fixed-width types include both signed and unsigned integer types such as int8_t, uint8_t, uint32_t, uint64_t, as well as constants that specify their limits.
Note the following when converting to LP64:
System-derived types, such as size_t, should be used for type declarations whenever possible.
Using derived types will help make driver code 32-bit and 64-bit safe, since the derived types themselves are safe for both IPL32 and LP64 data models. In addition, it is a good idea to use system-derived types for definitions to allow for future change.
Fixed-width types, such as uint32_t, should be used where appropriate to clearly specify type declarations.
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. In particular, make sure that use of variables of type long remains accurate. As a long is 64 bits in LP64, change variables that represent 32-bit data and that are currently defined as long to fixed-width 32-bit types, such as uint32_t.
The new derived types uintptr_t or intptr_t should be used as the integral type for pointers.
Pointers are 64 bits in size in the Solaris 64-bit environment. Although pointers could be recast to long, the new derived system types should be used instead, particularly when pointers are used to do address arithmetic or alignment. In addition, check for code that assumes that int and pointer variables are the same size, and use uintptr_t instead of int for variables that are cast to pointers. For example, change:
char *p; p = (char *)((int)p & PAGEOFFSET);
to:
p = (char *)((uintptr_t)p & PAGEOFFSET);
The new fixed width data types in <sys/inttypes.h> are included in <sys/ddi.h>.
Caution: Do not ignore compilation or lint warnings during conversion for LP64, because even those that were safe to ignore previously in the ILP32 environment may now indicate a more serious problem.
Running driver code through the Sun WorkShop Compiler C 4.2 lint utility can help find problems with data models. This version of lint provides warnings about potential 64-bit problems. It prints the line number of the problem code and a warning message describing the problem, indicates whether a pointer was involved, and provides information about the size of the data types.
To use lint(1B) to check for data model conversion problems, use the -errchk=longptr64 option.
In the 64-bit data model, data structures that use long to define the type of arguments might be incorrect if the argument needs to define a 32-bit quantity. For example, some drivers currently use long to define 32-bit fields in a hardware register layout. To make a driver 64-bit safe, update data structures where necessary to use int32_t or uint32_t, defined in <sys/inttypes.h>, instead of long for 32-bit data. This preserves the binary layout of 32-bit data structures.
For example, change:
struct reg { ulong_t addr; uint_t count; }
to:
struct reg { uint32_t addr; uint32_t count; }
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: relative time in specified resolution
bufcall_id_t: bufcall(9F) id
daddr_t: disk block address
ino_t: inode
intptr_t: integral pointer type
off_t: file offset
size_t: size of an object
ssize_t: size of an object or -1
time_t: time of day in seconds
timeout_id_t: timeout(9F) handler id
uintptr_t: unsigned integral pointer type
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.
Previously, DDI common access functions specified the size of data in terms of bytes, words, and so on. For example, ddi_getl(9F) was used to access 32-bit quantities. This function will not work to access a 32-bit quantity in the 64-bit environment because long is now a 64-bit quantity.
In Solaris 2.6, new common access functions that use fixed-width types have been added. These functions have been named to reflect the actual data size. For example, in a 64-bit environment, a driver must use ddi_get32(9F) to access 32-bit data rather than ddi_getl(9F).
uint32_t ddi_get32(ddi_acc_handle_t hdl, uint32_t *dev_addr);
To make a device driver 64-bit safe, replace all Common Access functions with the new fixed-width versions.
Table F-2 shows a subset of the new common access functions. For a complete list of the new functions, see Appendix B, Interface Transition List. For a brief description of the new interfaces, see Appendix C, Summary of Solaris 7 DDI/DKI Services.
Table F-2 Solaris 7 Common Access Functions
reads 8 bits from device address |
|
reads 16 bits from device address |
|
reads 32 bits from device address |
|
reads 64 bits from device address |
|
writes 8 bits to device address |
|
writes 16 bits to device address |
|
writes 32 bits to device address |
|
writes 64 bits to device address |
If a device driver shares data structures with an application using ioctl(9E), devmap(9E), or mmap(9E), and the driver is recompiled for a 64-bit kernel but the application that uses the interface is a 32-bit program, the binary layout of data structures will be incompatible if they contain long types or pointers.
If a data structure is defined in terms of type long, but there is no actual need for 64-bit data items, the data structure should be changed to use fundamental types that remain 32 bits in LP64 (int and unsigned int) or the new fixed-width 32-bit types in <sys/inttypes.h>. In the remaining cases, where the data structures contain pointers or structure fields that need to be long (32-bits in an ILP32 kernel and 64-bits in an LP64 kernel), the driver needs to be aware of the different structure shapes for ILP32 and LP64 and determine whether there is a model mismatch between the application and the kernel.
To handle potential data model differences, the ioctl(9E), devmap(9E) and mmap(9E) driver entry points, which are passed arguments from user applications, need to be written to determine whether the argument came from an application using the same data type model as the kernel. The new DDI function ddi_model_convert_from(9F) enables drivers to determine this.
This function takes the data type model of the user application as an argument and returns the following values:
DDI_MODEL_ILP32 - Convert from ILP32 application
DDI_MODEL_NONE - No conversion needed
DDI_MODEL_NONE is returned if no data conversion is necessary. This is the case when application and driver have the same data model (both are ILP32 or LP64). DDI_MODEL_ILP32 is returned if the driver is compiled to the LP64 data model and is communicating with a 32-bit application. Typically, the code that returns the application data model is conditionally compiled depending on the _MULTI_DATAMODEL macro. This macro is defined by the system when the driver supports multiple data models.
If the driver supports multiple data models, it will switch on the return value of ddi_model_convert_from(9F). The DDI_MODEL_ILP32 case should define a 32-bit version of the structure being passed in. Use ddi_copyin(9F) to copy the structure from user space to the 32-bit version of the structure, and then assign each field in the 32-bit structure to the 64-bit version. Otherwise, the code should be unchanged.
The sections that follow show code examples of the use of ddi_model_convert_from(9F).
In a 32-bit system, the ioctl(9E) entry point takes an int as the argument to pass a 32-bit value or user address to the device driver. In a 64-bit system, this argument must handle 64-bit values and addresses. Therefore, the ioctl(9E) function prototype has changed from:
int (*cb_ioctl)(dev_t dev, int cmd, int arg, int mode, cred_t *credp, int *rvalp);
to:
int (*cb_ioctl)(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp);
Note that intptr_t arg remains 32-bits when compiled in the ILP32 kernel.
To determine whether there is a model mismatch between the application and the driver, the driver uses the FMODELS mask to determine the model type from the ioctl(9E) mode argument. The following values are passed in mode to identify the application data model:
FLP64 - Application uses the LP64 data model
FILP32 - Application uses the ILP32 data model
The driver passes the data model type to ddi_model_convert_from(9F), which determines if adjustments are needed to the application data structures.
Example F-1 demonstrates the use of the _MULTI_DATAMODEL macro and the ddi_model_convert_from(9F) function.
struct passargs { int len; caddr_t addr } pa; xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { ... #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct passargs32 { int len; uint32_t *addr; } pa32; (void) ddi_copyin((void *)arg, &pa32, sizeof (struct passargs32), mode); pa.len = pa32.len; pa.addr = pa32.address; break; } case DDI_MODEL_NONE: (void) ddi_copyin((void *)arg, &pa, sizeof (struct passargs), mode); break; } #else /* ! _MULTI_DATAMODEL */ (void) ddi_copyin((void *)arg, &pa, sizeof (struct passargs), mode); #endif /* ! _MULTI_DATAMODEL */ do_ioctl(&pa); ... }
Data structure macros are another method of referring to the data structure from the user application. The macros effectively hide the difference between the data model of the user application and the driver. For more information see "Data Structure Macros".
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 consumed by the 32-bit application.
To determine whether there is a model mismatch, devmap(9E) uses the model parameter to pass the data model type expected by the application. model is set to one of the following:
DDI_MODEL_ILP32 - Application uses the ILP32 data model
DDI_MODEL_LP64 - Application uses the LP64 data model
Example F-2 shows the devmap(9E) model parameter being passed to the ddi_model_convert_from(9F) function.
struct data { int len; caddr_t addr; }; xxdevmap(dev_t dev, devmap_cookie_t dhp, offset_t offset, size_t len, size_t *maplen, uint_t model); { struct data dtc; /* local copy for clash resolution */ struct data *dp = (struct data *)shared_area; #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(model)) { case DDI_MODEL_ILP32: { struct data32 { int len; uint32_t *addr; } *da32p; da32p = (struct data32 *)shared_area; dp = &dtc; dp->len = da32p->len; dp->address = da32p->address; break; } case DDI_MODEL_NONE: break; } #endif /* _MULTI_DATAMODEL */ /* continues along using dp */ ... }
Because mmap(9E) does not have a parameter that can be used to pass data model information, the driver's mmap(9E) entry point should be written to use the new DDI function ddi_mmap_get_model(9F). This function returns one of the following values to indicate the application's data type model:
DDI_MODEL_ILP32 - Application expects the ILP32 data model
DDI_MODEL_ILP64 - Application expects the LP64 data model
DDI_FAILURE - Function was not called from mmap(9E)
As with ioctl(9E) and devmap(9E), the model bits can be passed to ddi_model_convert_from(9F) to determine whether data conversion is necessary.
Example F-3 shows the use of ddi_mmap_get_model(9F).
struct data { int len; caddr_t addr }; xxmmap(dev_t dev, off_t off, int prot) { struct data dtc; /* local copy for clash resolution */ struct data *dp = (struct data *)shared_area; #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(ddi_mmap_get_model())) { case DDI_MODEL_ILP32: { struct data32 { int len; uint32_t *addr } *da32p; da32p = (struct data32 *)shared_area; dp = &dtc; dp->len = da32p->len; dp->address = da32p->address; break; } case DDI_MODEL_NONE: break; } #endif /* _MULTI_DATAMODEL */ /* continues along using dp */ ... }
The data type of some of the fields in 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.
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 4GB in future systems.
This structure defines attributes of the DMA engine and the device. Since 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 */This 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.
u_int pkt_flags; /* was type u_long */ int pkt_time; /* was type long */ ssize_t pkt_resid; /* was type long */ u_int pkt_state; /* was type u_long */ u_int pkt_statistics; /* was type u_long */Since the flags, state and statistics fields 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.
Some of the DDI functions argument data types have been changed. These routines are listed below.
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 to be type unsigned long *.
In the 64-bit kernel, drv_getparm(9F) can be used to fetch quantities that are both 32-bit and 64-bit, yet the interface does not define the data types of these quantities which encourages simple programming errors.
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)(caddr_t), caddr_t arg, clock_t ticks);The ticks argument to both of these 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 both of these 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 since it 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 both of these routines was defined to be of type long. Since they 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 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 *.
Many ioctl operations are common to the same class of device driver. For example, most disk drivers implement many of the dkio(7I) family of ioctls. Many of these interfaces copy in or copy out data structures from the kernel, and some of these data structures have changed size as a result of the LP64 data model. The following section lists the ioctls that now require explicit conversion in 64-bit driver ioctl routines for the dkio(7I), fdio(7I), fbio(7I),cdio(7I) and mtio(7I) families of ioctls.
Table F-3 Data Structures
ioctl Command |
Affected Data Structure |
Reference |
---|---|---|
DKIOCGAPART | struct dk_map() | dkio(7I) |
DKIOCSAPART | struct dk_allmap() | |
DKIOGVTOC | struct partition() | dkio(7I) |
DKIOSVTOC | struct vtoc() | |
FBIOPUTCMAP | struct fbcmap() | fbio(7I) |
FBIOPUTCMAPI | struct fbcmap_i() | fbio(7I) |
FBIOGETCMAPI | ||
FBIOSCURSOR | struct fbcursor() | fbio(7I) |
FBIOSCURSOR | ||
CDROMREADMODE1 | struct cdrom_read() | cdio(7I) |
CDROMREADMODE2 | ||
CDROMCDDA | struct cdrom_cdda() | cdio(7I) |
CDROMCDXA | struct cdrom_cdxa() | cdio(7I) |
CDROMSUBCODE | struct cdrom_subcode() | cdio(7I) |
FDIOCMD | struct fd_cmd() | fdio(7I) |
FDRAW | struct fd_raw() | fdio(7I) |
MTIOCTOP | struct mtop() | mtio(7I) |
MTIOCGET | struct mtget() | mtio(7I) |
MTIOCGETDRIVETYPE | struct mtdrivetype_request() | mtio(7I) |
USCSICMD | struct uscsi_cmd() | Undocumented |
The nblocks property is exported by each slice of a block device drivers. It contains the number of 512 byte blocks that each slice of the device can support. The nblocks property is defined as a signed 32-bit quantity, which limits the maximum number of disk blocks to 1Tbyte.
Disk devices that provide more than 1Tbyte of storage per disk, must define the Nblocks property, which should still contain the number of 512 byte blocks that the device can support. However, Nblocks is a signed 64-bit quantity, which removes any practical limit on disk space.
The nblocks property is now deprecated; all disk devices should provide the Nblocks property.