Pages

Writing an Example Driver From Scratch: Chapter -3 fops implementatoin

From what we have learnt in the last two chapters, our code should look some thing like this



The next step now is to define the file operations that we wish to implement in our driver. For this we would need a structure of the kind file_operation. The structure is defined in linux/fs.h. The structure is basically a set of function pointers, and a driver can choose to implement what ever set of functions it wants to implement and declare them in the driver. For our case we had planned to support, read,write,open and release operations which can be done as follows.

struct file_operations fops = { read: read, write: write, open: open, release: release };

Please note that the format of the structure is very different from what you would come across in normal "c" structures. This format is specific to the kernel modules.

The column on the left is the name of the operation that we want to support and, the value on the right is the name of the function that will implement the operation. Which means "read" operation will be implemented by a function called "read", "write" operation will be implemented by a function called "write". The function names could be any thing of your choice as long as you use the same name in the structure as well as the actual implementation of the function.

By specifying the function names in this fashion using the file_operations structure, we are informing the kernel which function to call for which operation.

So when kernel wants to read from your device, it will get the pointer to function that will do the read operation from the file_operation structure.

Now that we have declared the operations supported by the driver, we have to actually implement the function in the driver.

Let us start from the "open" operation.

Open:

The open calls takes two arguments, the inode and the file pointer to the file being opened and should return 1 or 0 depending on failure or success of the function, respectively.

int open(struct inode *inode, struct file *filp)

For our device, there is nothing much we need to do in the open as we just have small array as a device.

We will just hold the semaphore so that only one process is allowed to open the device at a given time.

The function that will allow us to hold the semaphore is down_interruptible(struct *semaphore).

So for our device it would be

down_interruptible(&char_arr.sem)

If we fail to hold the semaphore we need to fail gracefully so we a put this call into a "if" condition.

if(down_interruptible(&char_arr.sem)) {
printk(KERN_INFO " could not hold semaphore");
return -1;
}

If any other process is holding the device, we would print the relevant message and fail.

In case there is no other process holding the device, we need to indicate success of the open call by a return 0

Once we have the device open, the next step would be either "read" data from the device or "write" data in to the device.

Let us look at the read function first.

Read :

The read function takes as arguments

A pointer to the struct file

A pointer to a char, which is a buffer into which the data from the device will be read into
A variable of the type size_t, a count of number bytes of data to be transfered into the buffer
A pointer of type loff_t, which is a pointer to the current position or the offset being read in the file.

The read function returns "ssize_t" which is the number of bytes read .

In our implementation of read, we need to put the data in our device, i.e. the array, which is the kernel space to the user space

. So we will make use of the function "copy_to_user" which takes the following arguments

A pointer to the location where data has to be copied , i.e. in our case the buffer that was passed to the read function
A Pointer the location from where the data has to be copied, i.e. in our case the array
A count of the number of bytes to be copied.


The function returns the number of bytes it could not read from the "count" number of bytes requested for. In case of successful read, it returns 0.

Therefore in our driver the call to "copy_to_user" would be

ret = copy_to_user(buff, char_arr.array, count);

Where "ret" is an unsigned long variable.

We will return the "ret" value as a return value of the read function. Thats is all that is required of our simple read function.

Write:

The write function is the opposite of read, we will have to write data into the device, i.e. in our case fill data into the array.

The write function also takes similar arguments as the read

A file pointer, to the file we are writing into.
A char buffer from which the data will be copied to the file or device.
A size_t variable to hold the number of bytes to be written
A pointer of type loff_t which will point to the current location of the file pointer in the file.


Therefore in our case the write function declaration would be

ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp)

In the case of "write" we have to copy data from the user space to the kernel space. To achieve this we will use "copy_from_user" which will transfer the requested number of bytes of data from the user space to the kernel space.

copy_from_user take the similar arguments as the copy_to_user

A pointer to the location where data has to be copied , i.e. in our case this would be the array, char_arr.array.
A Pointer the location from where the data has to be copied, i.e. in our case this would be the pointer to the buffer area.
A count of the number of bytes to be copied


The return value of copy_from_user is the amount of data that was written, in case all the data was written successfully 0 is returned.

So the copy from user function call would be

ret = copy_from_user(char_arr.array, buff, count);

We will have to return the "ret" to indicate the amount of data that was not written successfully.

The only file operation left to be implemented is release.

Release

In our device there is nothing that we have to do in release as the device is only a simple array. We had held a semaphore when we had done the open call, now we will release the semaphore.

A semaphore is released using the function call up(semaphore *)

Therefore we will have to call

up(&char_arr.sem);

Now that we have seen all the four operations that our device supports add them to our driver code. We will see the full code of our example module in the next chapter and also read and write from the device using a user application.

Next Chapter

Inserting into the kernel

1 comment:

  1. THESE FOUR CHAPTERS ARE GREAT..IT MAKES UNDERSTANDING THE TEXT SO MUCH EASIER..REALLY APPRECIATE THE EFFORT..THANK YOU :)

    ReplyDelete