Writing Device Drivers

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_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:

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.


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

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

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:

Example F-2 shows the devmap(9E) model parameter being passed to the ddi_model_convert_from(9F) function.


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:

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


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 */
	...
}