Writing Device Drivers
  Sök endast i den här boken
Ladda ner denna bok i PDF

Making a Device Driver 64-Bit Ready

F

In future releases, the Solaris kernel will be capable of running in 64-bit mode on suitable hardware and will support both 32-bit and 64-bit applications. This chapter is a guide to updating a device driver to run in a 64-bit environment.
The information in this chapter is being provided in advance of the 64-bit Solaris operating system so that driver writers will have sufficient time to develop 64-bit capable drivers before actually running the 64-bit kernel. For Solaris 2.6, the Solaris operating system is 32 bits only.
When the 64-bit operating system becomes available, applications that need 64-bit capabilities may need to be updated to run on 64-bit Solaris. Device drivers will require at least minimal conversion to run in the 64-bit kernel. For those applications that continue to use 32-bit Solaris on 64-bit hardware, 32-bit device drivers will continue to work without recompilation. The information in this chapter will enable drivers to be written to use common code for the 32-bit and 64-bit environments.

How 64-Bit Drivers Differ From 32-Bit Drivers

Before starting to convert a device driver for the 64-bit environment, it is useful to understand how the 32-bit environment differs from the 64-bit environment. In particular, it is important to become familiar with the C language data type models ILP32 and LP64, and to be aware of driver-specific issues.

C Language Data Type Models: LP64 and ILP32

The current 32-bit Solaris C language data model, called ILP32, defines int, long, and pointers as 32 bits, short as 16 bits, and char as 8 bits. The C data type model chosen for the 64-bit operating system is LP64. This data model defines long and pointers as 64 bits, int as 32 bits, short as 16 bits, and char as 8 bits.
In LP64, only longs and pointers change size; the other C data types stay the same size as in the ILP32 model. Table F-1 lists the standard C data types and their corresponding sizes in bits for ILP32 and LP64.
Table F-1
C TypeILP32LP64
char88
short1616
int3232
long3264
long long6464
pointer3264
In addition to the data model changes, some system-derived types, such as size_t, have been expanded to be 64-bit quantities when compiled in the 64-bit environment.

Potential Problems to Avoid

To run in a 64-bit environment, drivers may need to be converted to use the LP64 data model. There are several potential problem areas caused by the change in size of long and pointers:
  1. Source code that assumes that int, long, and pointer types are the same size will be incorrect for 64-bit Solaris.

  2. Type casts may need updating, since the underlying data types may have changed.

  1. Data structures containing long types and pointers will need to be checked for different offset values than expected. This is caused by alignment differences that occur when long and pointer fields grow to 64 bits.

Overview of Driver-Specific Issues

In addition to general code cleanup to support the data model changes for LP64, driver writers have several driver-specific issues to consider:
  • In the 64-bit environment, new common access functions that use fixed-width data types have been provided so that drivers can clearly specify the size of the data they are requesting. Drivers that use the old common access routines (for example, ddi_getw()) will need to be changed.
  • A driver may need to be updated to support data sharing between 64-bit drivers and 32-bit applications. The ioctl(9E), devmap(9E), and mmap(9E) entry points must be written so that the driver can determine whether the data model of the application is the same as that of the kernel. If the data models differ, data structures may need to be adjusted. This usually means converting a 64-bit a driver structure to fit in a 32-bit application structure.

General Conversion Steps

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 32-bit data in register layouts
  • Check use of derived types that change size in ILP32 and LP64
  • Change DDI common access functions to use the new fixed-width versions
  • Modify the driver entry points that handle data sharing

Convert Driver Code to Be 64-Bit Clean

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:
  1. 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 must be 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.

  2. 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.

  3. 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>.

Useful Tools to Check Data Model Conversion

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 to check for data model conversion problems, use the -errchk=longptr64 option.

Update Data Structures to Preserve 32-Bit Data in Register Layouts

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 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;
}

Check Use of Derived Types That Change Size Between 32-Bit and 64-Bit Environments

Some system-derived types, such as size_t, represent 32-bit quantities in a 32-bit system but represent 64-bit quantities in a 64-bit system. Drivers that use these derived types must pay attention to their use, particularly if they are assigning these values to variables of another derived type, such as a fixed-width type. Examples of derived types that change size are:
  • size_t
  • intptr_t
  • daddr_t

Change Common Access Functions to Fixed-Width Versions

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 on page 493 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 2.6 DDI/DKI Services".
Table F-2
Solaris 2.5 VersionSolaris 2.6 Version
ddi_getb(9F)ddi_get8(9F)
ddi_getw(9F)ddi_get16(9F)
ddi_getl(9F)ddi_get32(9F)
ddi_getll(9F)ddi_get64(9F)
ddi_putb(9F)ddi_put8(9F)
ddi_putw(9F)ddi_put16(9F)
ddi_putl(9F)ddi_put32(9F)
ddi_putll(9F)ddi_put64(9F)

Modify Routines That Handle Data Sharing

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.
ddi_model_convert_from(9F)

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).

ioctl(9E)

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.
Code Example F-1 demonstrates the use of the _MULTI_DATAMODEL macro and the ddi_model_convert_from(9F) function.
Code Example F-1 ioctl(9E)
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);
    ...
}

devmap(9E)

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
Code Example F-2 shows the devmap(9E) model parameter being passed to the ddi_model_convert_from(9F) function.
Code Example F-2 devmap(9E)
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 */
    ...
}

mmap(9E)

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.
Code Example F-3 shows the use of ddi_mmap_get_model(9F).
Code Example F-3 mmap(9E)
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 */
    ...
}