Step By Step Tutorial : Write Linux Device Driver Char

Now, let’s do a small exercise of writing Linux device driver char. Let’s assume that you have attached an imaginary character device named my_device with your computer. Let’s write driver for this device from whatever we have discussed so far.  

Step 1: Creating the device node

First step includes creating a device node in user space. Just go to your home directory and create a device node by using ‘mknod,.

# cd /home/llk

# mknod /dev/my_device c 256 0

Fig. 5.6 creating a device node.

With the above command, you have created a character device node my_device under /dev directory. Also, this device will be identified by major number 256 and minor number 0.

Step 2: Deciding the functions

Now just have a close look to your device and think what all operations it can perform. For now, assume our device is very simple and supports only simple open, read, write, and release functions. It means you can open the device, write into it or read from it and then release the device. So, after creating the device node, you have to identify all the operations that this device can perform. Once you identified all the device operations, you have to implement all those operations into different functions. For our device we have identified only open, read, write and release operations so let’s implement them in different functions.

int my_device_open(struct inode* inode, struct file* filep);

int my_device_write(struct file* filep, const char* buff, size_t count, loff_t*  offp);

int my_device_read(struct file* filep, const char* buff, size_t count, loff_t*  offp);

int my_device_release(struct inode* inode, struct file* filep);

Fig. 5.7  Declaration of device driver functions for our char device.

You can see that in the name of implementation, we have infact done nothing. These are the empty functions just for the sake of demontrating the step. We will continue our discussion and subsequently will keep on giving bodies to those functions.

Step 3: Populating file_operations

Once you have defined all the operations for your character driver, you have to create a struct file_operations object and populate the fields of this object with the address of your driver functions. file_operations structure contains function pointers of different driver operations. We have to assign these function pointers with the address of their respective functions. For this purpose we use special GNU C syntax. Using ‘pointer:address’ we can assign the elements of a structure.

static  struct file_operations f_ops =

{

open:my_device_open,

write:my_device_write,

read:my_device_read,

release:my_device_release

};

Above assignment of structure is not an ANSI C syntax that is the GNU C syntax. Now, there is also an ANSI C way of assigning structure element is available and in fact which should be preferred also, for portability reasons.  Below is the ANSI C way of allocating fops structure elements.

static  struct file_operations f_ops =

{

.open = my_device_open,

.write = my_device_write,

.read = my_device_read,

.release = my_device_release

};

So, the structure members of fops are initialized above way, the structure member that is not initialized will be set to NULL.

Step 4: Registering Device

We have populated the fops structure with all the functions. We will use these functions for doing operations on our device. So, we have to associate these functions with our device also. That is called rfegistering a device and is done using register_chrdev kernel function defined in linux/fs.h.

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

Its arguments are,

  1. major: Major number associate with the device driver.
  2. name: Name of the device for which we are writing driver. It should match with the device name that appears in /proc/devices list.
  3. fops: fops is the pointer to struct file_operations.

If the first argument, major number passed as 0, register_chrdev will dynamically allocate the major number and return it.

This registration will be done in the module initialization routine.

 

#define MY_MAJOR 256

 int my_device _int(void)

{

register_chrdev(MY_MAJOR, ”my_device’, fops);

return 0;

}

Fig. 5.8  Registering char device.

Register call tells kernel that major number of my device driver is and the device name is  my_device. So, it will search for a device file object with the name my_device and id 256. And this file object will store the address of file_operations, fops.

Step 5: Unregistering the device  

Whenever we no longer need the driver, we can unregister that module. For unregistering, we have to remove fops from that file object. For doing this, we just call ‘unregister_chrdev’ kernel function and specify the file name and the major number to this function.

unregister_chrdev(MY_MAJOR, ”my_device’);

We have to be cautious while unregistering. We have to make sure that no device is using this module when we unregister it. Unregistration of device is done in module cleanup routine.

#define MY_MAJOR 256

void mydevice_exit(void)

{

unregister_chrdev(MY_MAJOR, ‘my_device’);

}

Fig.  5.9  Unregistering char device.

Two APIs

Let’s take a small diversion and learn two kernel apis, copy_to_user and copy_from_user. A driver has to get the input from user , process it and then put the result back to the user. For this purpose, driver has to read from the user and also has to write back to the user as well. Linux kernel has provided specialized apis for this purpose. These are defined in <asm/uaccess.h>.

1)      copy_from_user

2)      copy_to_user

copy_from_user

This api copies a block of data from user space to the kernel space. Driver resides in the kernel space and uses copy_from_user to take input from user. Signature of this api is,

unsigned long copy_from_user (void* destination_address, const void* source_address, unsigned long num_of_bytes_to_copy);

This api copies a block of data from user space to kernel space. The return value of this api is somewhat special and requires your attention. This api returns number of bytes that could not be copied. So, if all the bytes are copied successfully, there will be nothing that could not be copied. So, when all bytes are copied successfully, this api will return zero. In case , when all the bytes could not be copied to the destination, the remaining bytes would be padded to zero.

copy_to_user

copy_to_user api is counterpart of the previous, copy_from_user api. This api copies a block of data from kernel space to user space.

Signature of this api is,

unsigned long copy_to_user (void* destination_address, const void* source_address, unsigned long num_of_bytes_to_copy);

Signature of both the functions , copy_to_user and copy_from_user looks same. The only difference lies in the source and destination addresses.

In  case of copy_from_user(),Remember you are reading the data from user (user space) and writing   that data to the driver ( kernel space ) . So, the source address will be of user space buffer and the destination address will be of kernel space buffer (the buffer you will create in your driver module).

While in case of copy_to_user(), the situation is reverse. You are reading the data from device driver (kernel space) and writing it back to the user (user space). So, in this case your source address will be the address of the buffer that you create in your driver module (kernel space) and the destination address will be the address of user space buffer.

Read More Post