DEVELOPING YOUR FIRST LINUX DEVICE DRIVER FOR LINUX - STAGE 2


30 Mar

Before reading this article, you must read Stage 1 of this article series.


What to expect from this article

The stage is going to contain:

1. How to register the driver with the kernel. I will also talk about what is the difference between registering a driver and loading or inserting a driver.

2. How to call registered functions

3. How to access device file from user space


Driver code and understanding

Please look at the code below:


/* File name is fld.c.

* The file has four key concepts here:

*

* 1. Registering the driver with kernel. For that we have used alloc_chrdev_region.

* This API gives us device id which have a major number and minor number of devices.

* There are many times where we need more than one device file and using this API we have.

* i.e. tty. We will use the device ID to create 3 device file which we can see using

* $ ls -l /dev/

*

* 2. class_create - create class for all devices under /sys/class

*

* 3. device_create - create all device under /sys/class/[our class]

*

* 4. cdev_init and cdev_add : VFS s reponsible to handle all our device file operation.

* the api help to register all functions under file_operations and

* cdev_init initialize the device structure while hand over this structure to

* VFS by calling cdev_add */

#include   /* All module related APIs are declared */

#include     /* declares all character device related functions*/

#include  /* for copy_to_user and copy_from_user*/

#include  /* kmalloc() */

 

static dev_t device_id;

static struct class *device_class;

static struct cdev c_dev;

/* Buffer to store data */

char *memory_buffer;

static int fld_open(struct inode *inodePtr, struct file *filePtr)

{

   printk(KERN_INFO "FLD Driver: open()\n");

   return 0;

}

static int fld_close(struct inode *inodePtr, struct file *filePtr)

{

   printk(KERN_INFO "FLD Driver: close()\n");

   return 0;

}

static ssize_t fld_read(struct file *filePtr, char __user *buf, size_t length, loff_t *f_pos)

{

   /* Transfering data to user space */

   copy_to_user(buf, memory_buffer, 1)

   if (*f_pos == 0) /* Changing reading position as best suits */

   {

       *f_pos += 1;

       return 1;

   }

   else

       return 0;

}

static ssize_t fld_write(struct file *filePtr, const char __user *buf, size_t length, loff_t *off)

{

   const char *tmp;

   tmp = buf + length - 1;

   copy_from_user(memory_buffer, tmp, 1);

   return 1;

}

static void __exit fld_exit(void)

{

   printk(KERN_INFO "FLD Exit Called");

   cdev_del(&c_dev);

   if (memory_buffer)

   {

       kfree(memory_buffer);

   }

   device_destroy(device_class, device_id);

   class_destroy(device_class);

   unregister_chrdev_region(device_id, 1);

}

 

static struct file_operations fld_fops =

   {

       .owner = THIS_MODULE,

       .open = fld_open,

       .release = fld_close,

       .read = fld_read,

       .write = fld_write};

/* for '__init'/'__exit' - find info in stage 1 article*/

static int __init fld_init(void)

{

   int ret = 0;

   printk(KERN_INFO "FLD Init Called");

   /* The below API dynamically figures out a free major number and

    * registers the cnt number of device file numbers starting from

    * , with the name. */

   /* Registers a range of char device numbers */

   /* When you load driver and run the command 'cat /proc/devices'

    * you can see the device file name as 'fld_device' for the driver */

   if ((ret = alloc_chrdev_region(&device_id, 0, 1, "fld_device")) < 0)

   {

       return ret;

   }

   /* Allocating memory for the buffer */

   memory_buffer = kmalloc(1, GFP_KERNEL);

   if (!memory_buffer)

   {

       ret = -ENOMEM;

       goto fail;

   }

   memset(memory_buffer, 0, 1);

   /* Kernel populates the appropriate device class & device info into

    * the '$ ls -l /sys/class/' you could see 'fld_class' */

   /* Create device class */

   if ((device_class = class_create(THIS_MODULE, "fld_class")) == NULL)

   {

       unregister_chrdev_region(device_id, 1);

       return -1;

   }

   /* Create device info */

   /* when you execute $ ls -l /sys/class/fld_class/', you can see device 'fld_dev' */

   if (device_create(device_class, NULL, device_id, NULL, "fld_dev") == NULL)

   {

       class_destroy(device_class);

       unregister_chrdev_region(device_id, 1);

       return -1;

   }

   /* Initialize the character device structure (struct cdev c_dev)

    * with that, using cdev_init()*/

   cdev_init(&c_dev, &fld_fops);

   /* to hand this structure to the VFS using the call cdev_add() */

   if (cdev_add(&c_dev, device_id, 1) == -1)

   {

       device_destroy(device_class, device_id);

       class_destroy(device_class);

       unregister_chrdev_region(device_id, 1);

       return -1;

   }

   return 0;

   fail:

       fld_exit();

       return ret;

}

/* Find info about module_init and module_exit in Stage 1 article code*/

module_init(fld_init);

module_exit(fld_exit);

/* Refer stage 1 article for explanation below. */

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Mukesh Krishna");

MODULE_DESCRIPTION("For self-learning LDD");

How to compile

Refer to stage 1 article.

How to view log message

Refer to stage 1 article.

How to do file operation

How To Open, Read, Write and Close the driver?

cat is a command which opens a file, reads it,and then closes the file.

We will use this command to open and read our driver which is a device file.


To see the major and minor number, run the following command:

$ cat /sys/class/fld_class/fld_dev/uevent

To read

$ cat /dev/fld_dev


To Write

echo -n First > /dev/fld_dev


How to see class file, device file and loaded device driver

See all class file i.e. fld_class


$ ls -l /sys/class/


See device file i.e. fld_dev

$ ls -l /sys/class/fld_class/

$ ls -l /dev/


See Loaded driver

$ lsmod


Comments
* The email will not be published on the website.