Writing Portable Device Drivers

Follow the kernel team's rules to make your drivers work on all architectures.

Almost all Linux kernel device drivers work on more than just one type of processor. This only happens because device-driver writers adhere to a few important rules. These rules include using the proper variable types, not relying on specific memory page sizes, being aware of endian issues with external data, setting up proper data alignment and accessing device memory locations through the proper interface. This article explains these rules, shows why it is important that they be followed and gives examples of them in use.

Internal Kernel Data Types

One of the most basic rules to remember when writing portable code is to be aware of how big you need to make your variables. Different processors define different variable sizes for int and long data types. They also differ in specifying whether a variable size is signed or unsigned. Because of this, if you know your variable size has to be a specific number of bits, and it has to be signed or unsigned, then you need to use the built-in data types. The following typedefs can be used anywhere in kernel code and are defined in the linux/types.h header file:

u8    unsigned byte (8 bits)
u16   unsigned word (16 bits)
u32   unsigned 32-bit value
u64   unsigned 64-bit value
s8    signed byte (8 bits)
s16   signed word (16 bits)
s32   signed 32-bit value
s64   signed 64-bit value

For example, the i2c driver subsystem has a number of functions that are used to send and receive data on the i2c bus:

s32 i2c_smbus_write_byte(struct i2c_client
    *client, u8 value);
s32 i2c_smbus_read_byte_data(struct i2c_client
    *client, u8 command);
s32 i2c_smbus_write_byte_data(struct i2c_client
    *client, u8 command, u8 value);
All of these functions return a signed 32-bit value and take an unsigned 8-bit value for either a value or command parameter. Because these data types are used, this code is portable to any processor type.

If your variables are going to be used in any code that can be seen by user-space programs, then you need to use the following exportable data types. Examples of this are data structures that get passed through ioctl() calls. Once again they are defined in the linux/types.h header file:

__u8   unsigned byte (8 bits)
__u16   unsigned word (16 bits)
__u32   unsigned 32-bit value
__u64   unsigned 64-bit value
__s8    signed byte (8 bits)
__s16   signed word (16 bits)
__s32   signed 32-bit value
__s64   signed 64-bit value

For example, the usbdevice_fs.h header file defines a number of different structures that are used to talk to USB devices directly from user-space programs. Here is the definition of the ioctl that is used to send a USB control message to the device:

struct usbdevfs_ctrltransfer {
    __u8 requesttype;
    __u8 request;
    __u16 value;
    __u16 index;
    __u16 length;
    __u32 timeout;  /* in milliseconds */
    void *data;
#define USBDEVFS_CONTROL_IOWR('U', 0, struct
One thing that has caused a lot of problems, as 64-bit machines are getting more popular, is the fact that the size of a pointer is not the same as the size of an unsigned integer. The size of a pointer is equal to the size of an unsigned long. This can be seen in the prototype for get_zeroed_page():
extern unsigned long FASTCALL
    (get_zeroed_page(unsigned int gfp_mask))
get_zeroed_page() returns a free memory page that has already been wiped clean with zeros. It returns an unsigned long that should be cast to the specific data type that you need. The following code snippet from the drivers/char/serial.c file in the rs_open() function shows how this is done:
static unsigned char *tmp_buf;
unsigned long page;
if (!tmp_buf) {
    page = get_zeroed_page(GFP_KERNEL);
    if (!page)
       return -ENOMEM;
    if (tmp_buf)
       tmp_buf = (unsigned char *)page;
There are some native kernel data types that you should use instead of trying to use an unsigned long. Some of these are: pid_t, key_t, gid_t, size_t, ssize_t, ptrdiff_t, time_t, clock_t and caddr_t. If you need to use any of these types in your code, please use the given data types; it will prevent a lot of problems.

Memory Issues

As we saw above in the example taken from drivers/char/serial.c, you can ask the kernel for a memory page. The size of a memory page is not always 4KB of data (as it is on i386). If you are going to be referencing memory pages, you need to use the PAGE_SHIFT and PAGE_SIZE defines.

PAGE_SHIFT is the number of bits to shift one bit left to get the PAGE_SIZE value. Different architectures define this to different values. Table 1 shows a short list of some architectures and the values of PAGE_SHIFT and the resulting value for PAGE_SIZE.

Table 1. Some Architectures and the Values of PAGE_SHIFT and the Resulting Value for PAGE_SIZE

Even on the same base architecture type, you can have different page sizes. This depends sometimes on a configuration option (like IA-64) or is due to different variants of the processor type (like on ARM).

The code snippet from drivers/usb/audio.c in Listing 1 shows how PAGE_SHIFT and PAGE_SIZE are used when accessing memory directly.

Listing 1. Accessing Memory Directly



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

re: interact wit pci using linux

sup's picture

i work on linux ver 2.2, i would like to interact with the pci bus and pci devices what apis and structures should i use and in which header file should i include for its proper functioning.

thanks and regards,


Why all the kernel drivers

Don's picture

This is a fine article on kernel drivers, but why write all these things as kernel drivers? The I2C interface (and the USB interface) both provide for access to devices from user-space. For many devices, this is all that is needed. Yes for file systems and network, etc. a kernel driver is needed, but why clutter up the kernel for every gadget that comes along?

It's a must read document

Bhupesh's picture

It's a must read document for beginners :)

Re: Writing Portable Device Drivers

Anonymous's picture

It's really a helpful article !

Re: Writing Portable Device Drivers

Anonymous's picture

Its a good artical for newbies entering into driver deveopment.
The artical is very cear and understanding.


Re: Writing Portable Device Drivers

Anonymous's picture

this is a good article specific to writing efficient and portable Linux device driver. It has cleared some of my confusion as well as raised a few.

I appreciate it.

nikhil bhargava

Hi,i wrote a device driver

anonymous's picture


i wrote a device driver for USB for FC6. after writing the code and compiling it, how could i make it start when the usb is plugged in? should i also use insmod and rmmod?