Saturday 22 December 2012

Writing character device driver

#include<linux/init.h>
#include<linux/device.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<asm/uaccess.h>
#include<linux/string.h>
#include<linux/cdev.h>
MODULE_LICENSE("GPL");
#define DEVICE_NAME "mychardev"
struct cdev *mydevice;
struct class *myclass;
static char Buffer[256];
static ssize_t device_read(struct file *file,char *buffer,size_t length,loff_t *offset);
static ssize_t device_write(struct file *file,const char *buffer,size_t length,loff_t *offset);
static int device_open(struct inode *inode,struct file *file);
static int device_release(struct inode *inode,struct file *file);
static struct file_operations fops=
{
    .read=device_read,
    .write=device_write,
    .open=device_open,
    .release=device_release,
};
static int hello_init(void)
{
    int error;
    mydevice=cdev_alloc();
    if((error=alloc_chrdev_region(&mydevice->dev,0,1,DEVICE_NAME)))
    {
        printk(KERN_ALERT "\nThere is problem in allocating the chrdev region.");
        return error;
    }
    mydevice->ops=&fops;
    mydevice->owner=THIS_MODULE;
    if((error=cdev_add(mydevice,mydevice->dev,1))<0)   
    {
        printk(KERN_ALERT "\nThere is a problem in adding cdev.");
        return error;
    }
    if((myclass=class_create(THIS_MODULE,DEVICE_NAME))==NULL)
    {
        printk(KERN_ALERT "\nThe class creation failed.");
        return -15;
    }
    device_create(myclass,NULL,mydevice->dev,NULL,DEVICE_NAME);
    printk(KERN_ALERT "\nThe device created successfully");
    return 0;
}
static int hello_exit(void)
{
    printk(KERN_ALERT"\nTrying to exit.");
    device_destroy(myclass,mydevice->dev);
    class_destroy(myclass);
    unregister_chrdev_region(mydevice->dev,1);
    cdev_del(mydevice);
    printk(KERN_ALERT "\nExiting the device successfully.");
    return 0;
}
static int device_open(struct inode *inode,struct file *filep)
{
    printk(KERN_ALERT "\nThe device has been opened successfully.");
    return 0;
}
static int device_release(struct inode *inode,struct file *filep)
{
    printk(KERN_ALERT "\nThe device has been closed successfully.");
    return 0;
}
static ssize_t device_read(struct file *file,char *buffer,size_t length,loff_t *offset)
{
    size_t totalbytes;
    long bytesnotcopied;
    totalbytes=strlen(Buffer);
    bytesnotcopied=copy_to_user(buffer,Buffer,totalbytes);
    return totalbytes-bytesnotcopied;
}
static ssize_t device_write(struct file *file,const char *buffer,size_t length,loff_t *offset)
{
    size_t bytesnotcopied;
    bytesnotcopied=copy_from_user(Buffer,buffer,length);
    printk(KERN_ALERT "%s ",Buffer);
    return (length-bytesnotcopied);
}
module_init(hello_init);
module_exit(hello_exit);


-------------------------------------------------------------------------------------------------------------------------------------

What are all the headers for ??

i. linux/init
       It is used for module_init() and module_exit(). Actually, these are macros and it tells the compiler that module_init() is used by the driver when it is loaded. It can be later on deleted as it will be having no other functions besides at start up. The module_exit() is used to deallocate the resources which we allocated when loading the driver through init functions.
ii.linux/device
      This header is used as the struct class and struct device are described here. These are actually needed to create a node in the filesystem like /sys. The struct class create a class entry in the class directory in the /sys with the name specified with DEVICE_NAME (in the above example). Similarly, the device is used to create a actual device node. The class is generally the accumulation of different devices.
iii.linux/module
     This header is needed as it is in this header that MODULE_LICENSE(), MODULE_AUTHOR(),etc are all described.
iv.linux/fs
     The file related operations and the struct file_operations is described in here.
v.asm/uaccess
     The inline duo functions copy_from_user and copy_to_user are described here. These two are used to take care of virtual memory as the address we are referring to might not be actually present but rather be need to swept out from the disk. Furthermore, it checks the validity of the pointer that is provided form the user space. First it checks it the read/write permission is there and it also checks if the pointer lies in the address space of the process. And it is architecture dependent thus will be present inside arch/(any architecture)/asm/uaccess.h
vi. linux/cdev
     The structure cdev is defined here.
----------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------
alloc_chrdev_region() : This function is used to dynamically allocate the device number for the device( i.e. Major and Minor). The best thing about it is that it is safer to user as there is no chance of contradicting the Major number with the existing one. There is another variation to this
register_chrdev_reigion() : If we are sure that we are not violating the Major number that is not using the same as other drivers then we can statically allocate the device using register_chrdev_region().
cdev_alloc() : It is used to allocate the memory for the struct cdev structure.
cdev_add():  It adds the struct cdev passed and makes it lives immediately.
class_create(): It is used to create an entry in the class directory in /sys.
device_create(): It is used to create a device entry.
copy_from_user():
copy_to_user(): described above
Similarly, all the other functions are used in the exit module to release the resources allocated in the init module.
---------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------

The mistakes I made 

i. Always include MODULE_LICENSE(" ") either "GPL" or "Dual BSD/GPL" .This is because the kernel and all the proprietary codes are released under "GPL" license so if you are using it or modifying or doing anything with it. The code must be in compliance with "GPL" license so we declared. If we forget to give MODULE_LICENSE(), we will get several errors.
ii. Run the user level program as root user or as the user who inserted the modules.
---------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------

Inserting into the kernel

i. Building as a module

Write a make file in any directory,
obj-m := chardevices.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Then run $make in the terminal.
Once it is built then use
$sudo insmod ./chardevices.ko
The sudo here is mandatory without which we cannot insert it.
Similarly, *.ko should be used not *.o.

ii.Building as a part of the kernel

i. Go to the linux/device/char/chardevices (this is our directory)
Create a directory and copy a code in this directory.
Write a Makefile here,
obj-$(CONFIG_MY_CHAR)    := chardevices.o
ii. In the Makefile of linux/device/char add the following line 
obj-$(CONFIG_MY_CHAR) += chardevices/ (this is usually to specify the directory)
iii. Now, go ahead in linux/device/char and modify the Kconfig to add our configuration ie MY_CHAR
config MY_CHAR
    tristate "This is my character device"
    default y
    help
(give anything)
Since we are using tristate, there can be three state viz:
i. m which means to build the driver as module and will not be the part of the kernel
ii. y which means to build the driver as a part of the kernel
ii. n

No comments :

Post a Comment