Linux - Device Driver Programming                        Home : www.sharetechnote.com

 

 

 

Device Driver Programming

 

One of the biggest role of any operating system is to manage and operate that are connected to the system (e.g, PC or Embedded system board etc). Operating System do these jobs using specific software called device driver. Personally to me, Operating System is a collection of device drivers. I know this would be a little bit misleading, but it is for sure that device driver would be biggest part of Operating System. Understanding the details of device driver would be the best way to understanding Operating System. One of the best way to understand 'something' is to make it on your own. This is the main motivation for me to study on Device Driver Programming.. at least it was the biggest driver for my initial study. I have gone through many documents from the internet, some free tutorials on YouTube and even from paid on-line lectures. Among all of these that I went through, the best one for me was the series of articles listed in the reference section in this page. I guess a lot of other tutorials that I found eleswhere seemed to be based on these articles.

 

Before you jump into programming, it would be a good idea to go through following pages to have general understandings of device drivers and the related tools/commands. I would suggest just read through and don't spend too much time on it even if there is things which doesn't look clear to you. Once you try programming and then go back to these pages, thing would get clearer.

 

 

What I write on this page is mostly to follow through the articles in the reference sections with a little bit of modification and a little bit of my comments, but in much compressed way so that I can refer to this page as a cheat sheet. You may use my note as a quick reference as well, but I strongly recommend you to read full contents of the articles (at least the first 5 or 6 articles) in the reference section.

 

 

 

Device Driver HelloWorld

 

This is just to show you the minimum code structure of device driver and procedure to load the driver to the system and unload it from the system. I will not explain much about the meaning of each line of code. Just follow this procedure and see if it works with your system. Once it works and you can get a general idea of how to load/unload the driver (module) and then try to understand the detailed meaning of source code and makefile.

 

In any directory on your system, create two files as shown below.

    $ ls -al

     

    -rwxrwxr-x 1 sharetechnote sharetechnote   145 Jan 21 21:21 Makefile

    -rwxrwxrwx 1 sharetechnote sharetechnote   443 Jan 21 21:24 testdriver.c

 

Contents of these two files are shown as below.

 

testdriver.c

#include <linux/init.h>

#include <linux/module.h>

 

static int __init testdriver_init(void)

{

    printk("Test Driver Initialized\n");

        return 0;

}

 

static void __exit testdriver_exit(void)

{

        printk("Test Driver Being Removed\n");

}

 

module_init(testdriver_init);

module_exit(testdriver_exit);

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("ShareTechnote");

MODULE_DESCRIPTION("Test Driver : The Simplest Driver");

MODULE_VERSION("1.0.0");

 

Followings are short comments about the key functions and macros in this source.

  • module_init() and module_exit() are defined in module.h
  • MODULE_* macros populate module-related information, which acts like the module’s “signature”

 

 

Makefile

obj-m += testdriver.o

 

KDIR  := /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)

 

default:

    $(MAKE) -C $(KDIR) M=$(PWD) modules

          NOTE : Make it sure that the following highlighted part is 'TAB' (not whitespace). If you put whitespace here,

        you would have error during compilation with make program.

     default:

          $(MAKE) -C $(KDIR) M=$(PWD) modules

 

 

Then build the files using a make program as shown below.

 

    $ make

     

    // Following is the result of make on my Ubuntu. I am putting this output for your reference.

    make -C /lib/modules/4.13.0-26-generic/build M=/home/sharetechnote/driver/ex1 modules

    make[1]: Entering directory '/usr/src/linux-headers-4.13.0-26-generic'

      CC [M]  /home/sharetechnote/driver/ex1/testdriver.o

      Building modules, stage 2.

      MODPOST 1 modules

      CC      /home/sharetechnote/driver/ex1/testdriver.mod.o

      LD [M]  /home/sharetechnote/driver/ex1/testdriver.ko

    make[1]: Leaving directory '/usr/src/linux-headers-4.13.0-26-generic'

 

 

Now list the files in the directory and see what kind of files are created. Followings are the list of files created in my system.  testdriver.ko is the binary file for the sample driver.   

 

    $ ls -al

     

    total 168

    drwxrwxrwx 3 sharetechnote sharetechnote  4096 Jan 21 21:24 .

    drwxrwxr-x 4 sharetechnote sharetechnote  4096 Jan 21 21:11 ..

    -rw-rw-r-- 1 sharetechnote sharetechnote   243 Jan 21 21:18 .helloworld.ko.cmd

    -rw-rw-r-- 1 sharetechnote sharetechnote 28315 Jan 21 21:18 .helloworld.mod.o.cmd

    -rw-rw-r-- 1 sharetechnote sharetechnote 28207 Jan 21 21:18 .helloworld.o.cmd

    -rwxrwxr-x 1 sharetechnote sharetechnote   145 Jan 21 21:21 Makefile

    -rwxrwxr-x 1 sharetechnote sharetechnote   145 Jan 21 21:18 Makefile~

    -rw-rw-r-- 1 sharetechnote sharetechnote    47 Jan 21 21:24 modules.order

    -rw-rw-r-- 1 sharetechnote sharetechnote     0 Jan 21 21:24 Module.symvers

    -rwxrwxrwx 1 sharetechnote sharetechnote   443 Jan 21 21:24 testdriver.c

    -rwxrwxrwx 1 sharetechnote sharetechnote   384 Jan 18 23:13 testdriver.c~

    -rw-rw-r-- 1 sharetechnote sharetechnote  3824 Jan 21 21:24 testdriver.ko

    -rw-rw-r-- 1 sharetechnote sharetechnote   243 Jan 21 21:24 .testdriver.ko.cmd

    -rw-rw-r-- 1 sharetechnote sharetechnote   542 Jan 21 21:24 testdriver.mod.c

    -rw-rw-r-- 1 sharetechnote sharetechnote  2536 Jan 21 21:24 testdriver.mod.o

    -rw-rw-r-- 1 sharetechnote sharetechnote 28315 Jan 21 21:24 .testdriver.mod.o.cmd

    -rw-rw-r-- 1 sharetechnote sharetechnote  2136 Jan 21 21:24 testdriver.o

    -rw-rw-r-- 1 sharetechnote sharetechnote 28207 Jan 21 21:24 .testdriver.o.cmd

    drwxrwxr-x 2 sharetechnote sharetechnote  4096 Jan 21 21:24 .tmp_versions

 

 

Now you can load the driver file into the system using insmod command as shown below.

    $ sudo insmod testdriver.ko

     

 

Then you can confirm whether it is successfully loaded or not using lsmod command as shown below.

    $ lsmod | grep 'testdriver'

     

    testdriver             16384  0

 

Now you can unload the driver file from the system using rmmod command as shown below.

    $ sudo rmmod testdriver

     

 

Confirm that the driver is not found from the lsmod output.

    $ lsmod | grep 'testdriver'

     

 

Now you can confirm on what has happended during the load and unload process of the driver using dmesg command as shown below.

    $ dmesg

    .....

    [16172.548779] Test Driver Initialized

    [16211.160144] Test Driver Being Removed

 

This example showed the minimized code just for initializing/closing and loading/unloading the driver. However, with this code, the driver is not really running as a device driver. To confirm this, you may try following

    $ cat < /proc/device | grep 'test'

You wouldn't get any output for this meaning that the driver is not really running now. In next example, we will see how we can register it as a running process.

 

 

NOTE : For next tutorial, I would suggest you do followings (manual cleanup) before you move to the next section.  

    $ sudo rmmod testdriver

 

 

 

Improving MakeFile

 

The Makefile in previous section was written to be as simple as possible. But it builds the program only when there is some changes in the source code. However, sometimes you may see following error when you try to load the module (insmod) again.

    insmod: ERROR: could not insert module xxxxx.ko: Invalid module format

One possible cause for this error is that your kernel has been upgraded after you built the module. If the cause of the problem is due to the kernel upgrade, the easy fix is that delete the all intermediate files and rebuid the code and than try 'insmod'.

 

With the following Makefile, you can easily removes all the intermediate files and rebuild the code.

 

Makefile

obj-m += testdriver.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 try followings to remove the intermediate files from previous build.

    $ make clean

Then try followings to build the code.

    $ make

 

 

 

Registering the Driver

 

 

Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.

 

testdriver.c

#include <linux/module.h>

#include <linux/version.h>

#include <linux/kernel.h>

#include <linux/types.h>

#include <linux/kdev_t.h>

#include <linux/fs.h>

 

static dev_t first;

 

static int __init testdriver_init(void)

{

    printk("Test Driver Initialized\n");

    if (alloc_chrdev_region(&first, 0, 3, "TestDriver") < 0)

        {

            return -1;

        }

        printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));

 

        return 0;

}

 

static void __exit testdriver_exit(void)

{

        unregister_chrdev_region(first, 3);

        printk("Test Driver Being Removed\n");

}

 

module_init(testdriver_init);

module_exit(testdriver_exit);

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("ShareTechnote");

MODULE_DESCRIPTION("Test Driver : The Simplest Driver");

MODULE_VERSION("1.0.0");

 

 

Now build(make) the code and then load the driver file into the system using insmod command as shown below.

    $ sudo insmod testdriver.ko

     

 

Then you can confirm whether it is successfully loaded or not using lsmod command as shown below.

    $ lsmod | grep 'testdriver'

     

    testdriver             16384  0

 

Now you are getting a process assigned to this driver as shown below.

    $ cat /proc/devices | grep "Test"

    244 TestDriver  <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")

    // By this function, Kernel load the driver and assigns a Major number and return the number

    // to the parameter 'first'. The Kernel assigns the name 'TestDriver' to the driver.

    // this function request kernel 3 minor numbers starting from number 0.

    // If you are not familiar with Major / Minor number, see this topic.

 

 

Now you can confirm on what has happended during the load and unload process of the driver using dmesg command as shown below.

    $ dmesg

    .....

    [58699.029689] Test Driver Initialized

    [58699.029690] <Major, Minor>: <244, 0> <-- Kernel assigns 244 as Major number of the device

                                            // assigns 0 as the first minor number of the device

 

Now let's check if this device is created in /dev directly. You can check on this as follows.

    $ ls -l /dev | grep '244'

You would not see any output for this command meaning that the 'testdriver' is not created in /dev. In the next section, we will see how to make this happen.

 

NOTE : For next tutorial, I would suggest you do followings (manual cleanup) before you move to the next section.  

    $ sudo rmmod testdriver

 

 

 

Creating the Driver in /dev

 

 

Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.

 

testdriver.c

#include <linux/kernel.h>

#include <linux/types.h>

#include <linux/kdev_t.h>

#include <linux/fs.h>

#include <linux/device.h>

#include <linux/cdev.h>

 

static dev_t first;

static struct class *cl;

 

static int __init testdriver_init(void)

{

    printk("Test Driver Initialized\n");

    if (alloc_chrdev_region(&first, 0, 3, "TestDriver") < 0)

    {

        return -1;

    }

    printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));

 

    if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)

    {

        printk(KERN_INFO "class_create() failed");

        unregister_chrdev_region(first, 1);

        return -1;

    }

 

    if (device_create(cl, NULL, first, NULL, "testdriver") == NULL)

    {

        printk(KERN_INFO "device_create() failed"); 

        class_destroy(cl);

        unregister_chrdev_region(first, 1);

        return -1;

    }

 

    return 0;

}

 

static void __exit testdriver_exit(void)

{

    unregister_chrdev_region(first, 3);

    device_destroy(cl, first);

    class_destroy(cl);

    unregister_chrdev_region(first, 1);

    printk("Test Driver Being Removed\n");

}

 

module_init(testdriver_init);

module_exit(testdriver_exit);

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("ShareTechnote");

MODULE_DESCRIPTION("Test Driver : The Simplest Driver");

MODULE_VERSION("1.0.0");

 

Now build(make) the code and then load the driver file into the system using insmod command as shown below.

    $ sudo insmod testdriver.ko

     

 

Then you can confirm whether it is successfully loaded or not using lsmod command as shown below.

    $ lsmod | grep 'testdriver'

     

    testdriver             16384  0

 

Now you are getting a process assigned to this driver as shown below.

    $ cat /proc/devices | grep "Test"

    244 TestDriver  <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")

 

Now let's check if this device is created in /dev directly. You can check on this as follows.

    $ ls -l /dev | grep '244'

     

    crw-------  1 root root    244,   0 Jan 22 12:21 testdriver

    // this result is obtained by device_create(cl, NULL, first, NULL, "testdriver")

 

NOTE : For next tutorial, I would suggest you do followings (manual cleanup) before you move to the next section.  

    $ sudo rmmod testdriver

 

 

 

Completing the Dummy Driver Template

 

 

Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.

 

testdriver.c

#include <linux/module.h>

#include <linux/version.h>

#include <linux/kernel.h>

#include <linux/types.h>

#include <linux/kdev_t.h>

#include <linux/fs.h>

#include <linux/device.h>

#include <linux/cdev.h>

 

static dev_t first;

static struct class *cl;

static struct cdev char_dev;

 

static int testdriver_open(struct inode *i, struct file *f)

{

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

    return 0;

}

 

static int testdriver_close(struct inode *i, struct file *f)

{

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

    return 0;

}

 

static ssize_t testdriver_read(struct file *f, char __user *buf, size_t  len,

                               loff_t *off)

{

    printk(KERN_INFO "Driver: read()\n");

    return 0;

}

 

 

static ssize_t testdriver_write(struct file *f, const char __user *buf, size_t len,

                                loff_t *off)

{

    printk(KERN_INFO "Driver: write()\n");

    return len;

}

 

 

static struct file_operations testdriver_fops =

{

  .owner = THIS_MODULE,

  .open = testdriver_open,

  .release = testdriver_close,

  .read = testdriver_read,

  .write = testdriver_write

};

 

 

static int __init testdriver_init(void)

{

    printk("Test Driver Initialized\n");

    if (alloc_chrdev_region(&first, 0, 3, "TestDriver") < 0)

        {

            return -1;

        }

        printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));

 

    if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)

    {

        printk(KERN_INFO "class_create() failed");

            unregister_chrdev_region(first, 1);

            return -1;

    }

 

    if (device_create(cl, NULL, first, NULL, "testdriver") == NULL)

    {

        printk(KERN_INFO "device_create() failed"); 

            class_destroy(cl);

            unregister_chrdev_region(first, 1);

            return -1;

    }

 

 

        cdev_init(&char_dev, &testdriver_fops);

 

    if (cdev_add(&char_dev, first, 1) == -1)

    {

            device_destroy(cl, first);

            class_destroy(cl);

            unregister_chrdev_region(first, 1);

            return -1;

    }

 

 

        return 0;

}

 

static void __exit testdriver_exit(void)

{

    unregister_chrdev_region(first, 3);

    device_destroy(cl, first);

    class_destroy(cl);

    unregister_chrdev_region(first, 1);

        printk("Test Driver Being Removed\n");

}

 

module_init(testdriver_init);

module_exit(testdriver_exit);

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("ShareTechnote");

MODULE_DESCRIPTION("Test Driver : The Simplest Driver");

MODULE_VERSION("1.0.0");

 

Now build(make) the code and then load the driver file into the system using insmod command as shown below.

    $ sudo insmod testdriver.ko

     

 

Then you can confirm whether it is successfully loaded or not using lsmod command as shown below.

    $ lsmod | grep 'testdriver'

     

    testdriver             16384  0

 

Now you are getting a process assigned to this driver as shown below.

    $ cat /proc/devices | grep "Test"

    244 TestDriver  <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")

 

Now let's check if this device is created in /dev directly. You can check on this as follows.

    $ ls -l /dev | grep '244'

     

    crw-------  1 root root    244,   0 Jan 22 12:21 testdriver

    // this result is obtained by device_create(cl, NULL, first, NULL, "testdriver")

 

 

NOTE : now I will do some read/write test for the driver, but to do this you need to get root authority for the system. In some Linux, you can obtain root authority by following command and type in password

    $ su

But in my Ubuntu, su command always returned 'Authentication failure' error even though I typed in the correct password. With some Googling, I learned that switching to root with su command is blocked by default and we need to use following command. It worked.

    $ sudo -i

 

 

Now let's check what will happen when we write something to the device driver we implemented. You can write something to the driver using echo command as below.

 

    $ echo 'test write' > /dev/testdriver

 

You wouldn't see anything with this command because there is nothing returned from the driver to user level. But you can at least check on the result of printk() using dmesg command as shown below.

     

    $ dmesg | tail -10

    ...

    [93260.880872] Driver: open()

    [93260.880890] Driver: write()

    [93260.880894] Driver: close()

 

Now let's check what will happen when we read from the device driver we implemented. You can write something to the driver using echo command as below.

    $ cat /dev/testdriver

 

You wouldn't see anything with this command because there is nothing returned from the driver to user level. But you can at least check on the result of printk() using dmesg command as shown below.

    $ dmesg | tail -10

    ...

    [93260.880872] Driver: open()

    [93260.880890] Driver: write()

    [93260.880894] Driver: close()

 

 

 

Accessing the driver from a User Level C Program

 

 

In most case, the major usage of device driver is to provide an interface software between Linux kernel and user level program. So in this section, I would expand the driver code a little bit further and show you how to verify the functionaliry with a user level C program. This code still does not get any hardware involved as most of device driver do, but this can be a minimum template for writing device driver for any hardware.

 

Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.

 

testdriver.c

#include <linux/module.h>

#include <linux/version.h>

#include <linux/kernel.h>

#include <linux/types.h>

#include <linux/kdev_t.h>

#include <linux/fs.h>

#include <linux/device.h>

#include <linux/cdev.h>

#include <linux/uaccess.h>

 

static dev_t first;

static struct class *cl;

static struct cdev char_dev;

 

static int testdriver_open(struct inode *i, struct file *f)

{

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

    return 0;

}

 

static int testdriver_close(struct inode *i, struct file *f)

{

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

    return 0;

}

 

static ssize_t testdriver_read(struct file *f, char __user *buf, size_t  len,

                               loff_t *off)

{

    int retval = 0;

    char charArray[255] = "Hello World !\0";

 

    // In this routine, we want to transfer the data from the driver in kernel space

    // to a user program running on user space. Since they are running in different

    // spaces, simple pointer assignment does not work.

    // copy_to_user() is the kernel space API that copy a block (array) of data

    // from kernel space to user space.

    if (copy_to_user(buf, charArray, 15))

            retval = -ENODEV;

        else

            retval = 12;

 

    printk(KERN_INFO "Driver: read() - %s\n",charArray);

    return retval;

}

 

 

static ssize_t testdriver_write(struct file *f, const char __user *buf, size_t len,

                               loff_t *off)

{

    printk(KERN_INFO "Driver: write() - %s\n", buf);

    return len;

}

 

 

static struct file_operations testdriver_fops =

{

  .owner = THIS_MODULE,

  .open = testdriver_open,

  .release = testdriver_close,

  .read = testdriver_read,

  .write = testdriver_write

};

 

 

static int __init testdriver_init(void)

{

    printk("Test Driver Initialized\n");

   

    if (alloc_chrdev_region(&first, 0, 3, "TestDriver") < 0)

    {

            return -1;

    }

    printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));

 

    if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)

    {

        printk(KERN_INFO "class_create() failed");

        unregister_chrdev_region(first, 1);

        return -1;

    }

 

    if (device_create(cl, NULL, first, NULL, "testdriver") == NULL)

    {

        printk(KERN_INFO "device_create() failed"); 

        class_destroy(cl);

        unregister_chrdev_region(first, 1);

        return -1;

    }

 

 

    cdev_init(&char_dev, &testdriver_fops);

 

    if (cdev_add(&char_dev, first, 1) == -1)

    {

            device_destroy(cl, first);

            class_destroy(cl);

            unregister_chrdev_region(first, 1);

            return -1;

    }

 

 

        return 0;

}

 

 

static void __exit testdriver_exit(void)

{

    unregister_chrdev_region(first, 3);

    device_destroy(cl, first);

    class_destroy(cl);

    unregister_chrdev_region(first, 1);

    printk("Test Driver Being Removed\n");

}

 

module_init(testdriver_init);

module_exit(testdriver_exit);

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("ShareTechnote");

MODULE_DESCRIPTION("Test Driver : The Simplest Driver");

MODULE_VERSION("1.0.0");

 

Now build(make) the code and then load the driver file into the system using insmod command as shown below.

    $ sudo insmod testdriver.ko

     

 

Then you can confirm whether it is successfully loaded or not using lsmod command as shown below.

    $ lsmod | grep 'testdriver'

     

    testdriver             16384  0

 

Now you are getting a process assigned to this driver as shown below.

    $ cat /proc/devices | grep "Test"

    244 TestDriver  <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")

 

Now let's check if this device is created in /dev directly. You can check on this as follows.

    $ ls -l /dev | grep '244'

     

    crw-------  1 root root    244,   0 Jan 22 12:21 testdriver

    // this result is obtained by device_create(cl, NULL, first, NULL, "testdriver")

 

Now we have a very important steps to do. For a user level program to get access to the device driver, the driver file should get proper properties. The simplest way to do it is to change mode as shown below.

 

    $ sudo chmod 777 /dev/testdriver

 

Confirm that the driver file gets the proper attribute as below.

 

    $ ls -al /dev/testdriver

     

    crwxrwxrwx 1 root root 244, 0 Jan 23 17:32 /dev/testdriver

 

Now write a user level C programming (ordinary C programming you would usually wright) as bellow.

 

rwtest.c

#include <unistd.h>

#include <fcntl.h>        

#include <stdio.h>

 

int main()

{

 

    int fd;

    int ret = 0;

    char buff[100];

 

    fd = open("/dev/testdriver",O_RDWR);

        

    if(fd < 0) {

        printf("/dev/testdriver open failed (ret = %d)\n",fd);

        return 1;   

    };

 

    ret = read(fd,buff,10);

    printf("%s\n",buff);    

 

    ret = write(fd,"Hello",6);

    

    close(fd);

 

    return 1;

 

}

 

 

Then compile the code using gcc as follows (In Ubuntu that I am using, gcc was installed by defaut. But you can install it manually if gcc compiler is not installed on your system).

 

    $ gcc rwtest.c -o rwtest

 

If the code is compiled OK, run the program as below.

 

    $ ./rwtest

     

    Hello World !  <-- this is done by read() which is calling testdriver_read() in driver program

                            <-- you don't see any result of write() which is calling testdriver_write() in driver program

 

You don't see any result for write() function, it is because in the driver program 'testdriver_write()' write something using printk(). To confirm on the result of write() function you need to use dmesg command as shown below.

 

    $ dmesg | tail -10

     

    [ 5561.387036] Test Driver Initialized

    [ 5561.387037] <Major, Minor>: <244, 0>

    [ 5618.999309] Driver: open()

    [ 5618.999312] Driver: read() - Hello World !

    [ 5618.999343] Driver: write() - Hello

    [ 5618.999344] Driver: close()

 

 

 

Changing Permission to dev/testdriver

 

 

Now let's revise our code as follows. The part highlighed in red is the new code that are added in this section.

 

testdriver.c

#include <linux/module.h>

#include <linux/version.h>

#include <linux/kernel.h>

#include <linux/types.h>

#include <linux/kdev_t.h>

#include <linux/fs.h>

#include <linux/device.h>

#include <linux/cdev.h>

#include <linux/uaccess.h>

 

static dev_t first;

static struct class *cl;

static struct cdev char_dev;

 

static int testdriver_uevent(struct device *dev, struct kobj_uevent_env *env)

{

    add_uevent_var(env, "DEVMODE=%#o", 0666);

    return 0;

}

 

static int testdriver_open(struct inode *i, struct file *f)

{

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

    return 0;

}

 

static int testdriver_close(struct inode *i, struct file *f)

{

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

    return 0;

}

 

static ssize_t testdriver_read(struct file *f, char __user *buf, size_t  len, loff_t *off)

{

    int retval = 0;

    char charArray[255] = "Hello World !\0";

 

    if (copy_to_user(buf, charArray, 15)) {         

        printk(KERN_INFO "Driver: read() - copy_to_user() fail\n"); 

            retval = -ENODEV;

        } else {

            printk(KERN_INFO "Driver: read() - copy_to_user() success\n");

            printk(KERN_INFO "Driver: read() - %s\n",charArray);

            retval = 12;

        };

 

    if(off > 0) {

        printk(KERN_INFO "Driver: read() - Finished Reading\n");

        return 0;   

    };

 

 

    return retval;

}

 

 

static ssize_t testdriver_write(struct file *f, const char __user *buf, size_t len, loff_t *off)

{

    printk(KERN_INFO "Driver: write() - %s\n", buf);

    return len;

}

 

 

static struct file_operations testdriver_fops =

{

  .owner = THIS_MODULE,

  .open = testdriver_open,

  .release = testdriver_close,

  .read = testdriver_read,

  .write = testdriver_write

};

 

 

static int __init testdriver_init(void)

{

 

    printk("Test Driver Initialized\n");

 

    if (alloc_chrdev_region(&first, 0, 3, "TestDriver") < 0)

    {

            return -1;

    }

    printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));

 

    if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)

    {

        printk(KERN_INFO "class_create() failed");

            unregister_chrdev_region(first, 1);

            return -1;

    }

 

    cl->dev_uevent = testdriver_uevent;

 

    if (device_create(cl, NULL, first, NULL, "testdriver") == NULL)

    {

        printk(KERN_INFO "device_create() failed"); 

            class_destroy(cl);

            unregister_chrdev_region(first, 1);

            return -1;

    }

 

 

    cdev_init(&char_dev, &testdriver_fops);

 

    if (cdev_add(&char_dev, first, 1) == -1)

    {

            device_destroy(cl, first);

            class_destroy(cl);

            unregister_chrdev_region(first, 1);

            return -1;

    }

 

 

        return 0;

}

 

static void __exit testdriver_exit(void)

{

    unregister_chrdev_region(first, 3);

    device_destroy(cl, first);

    class_destroy(cl);

    unregister_chrdev_region(first, 1);

    printk("Test Driver Being Removed\n");

}

 

module_init(testdriver_init);

module_exit(testdriver_exit);

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("ShareTechnote");

MODULE_DESCRIPTION("Test Driver : The Simplest Driver");

MODULE_VERSION("1.0.0");

 

Now build(make) the code and then load the driver file into the system using insmod command as shown below.

    $ sudo insmod testdriver.ko

     

 

Then you can confirm whether it is successfully loaded or not using lsmod command as shown below.

    $ lsmod | grep 'testdriver'

     

    testdriver             16384  0

 

Now you are getting a process assigned to this driver as shown below.

    $ cat /proc/devices | grep "Test"

    244 TestDriver  <-- this is from alloc_chrdev_region(&first, 0, 3, "TestDriver")

 

Now let's check if this device is created in /dev directly. You can check on this as follows.

    $ ls -l /dev | grep '244'

     

    crw-rw-rw-   1 root root    244,   0 Jan 22 12:21 testdriver

    // this result is obtained by device_create(cl, NULL, first, NULL, "testdriver")

    // rw-rw-rw- is set by testdriver_uevent()

 

 

Now write a user level C programming (ordinary C programming you would usually wright) as bellow.

 

rwtest.c

#include <unistd.h>

#include <fcntl.h>        

#include <stdio.h>

 

int main()

{

 

    int fd;

    int ret = 0;

    char buff[100];

 

    fd = open("/dev/testdriver",O_RDWR);

        

    if(fd < 0) {

        printf("/dev/testdriver open failed (ret = %d)\n",fd);

        return 1;   

    };

 

    ret = read(fd,buff,10);

    printf("%s\n",buff);    

 

    ret = write(fd,"Hello",6);

    

    close(fd);

 

    return 1;

 

}

 

 

Then compile the code using gcc as follows (In Ubuntu that I am using, gcc was installed by defaut. But you can install it manually if gcc compiler is not installed on your system).

 

    $ gcc rwtest.c -o rwtest

 

If the code is compiled OK, run the program as below.

 

    $ ./rwtest

     

    Hello World !  <-- this is done by read() which is calling testdriver_read() in driver program

                            <-- you don't see any result of write() which is calling testdriver_write() in driver program

 

You don't see any result for write() function, it is because in the driver program 'testdriver_write()' write something using printk(). To confirm on the result of write() function you need to use dmesg command as shown below.

 

    $ dmesg | tail -10

     

    [ 5561.387036] Test Driver Initialized

    [ 5561.387037] <Major, Minor>: <244, 0>

    [ 5618.999309] Driver: open()

    [ 5618.999312] Driver: read() - Hello World !

    [ 5618.999343] Driver: write() - Hello

    [ 5618.999344] Driver: close()

 

 

 

 

Reference :

 

[ 1] Device Drivers, Part 1: Linux Device Drivers for Your Girl Friend

[ 2] Device Drivers, Part 2: Writing Your First Linux Driver in the Classroom  

[ 3] Device Drivers, Part 3: Kernel C Extras in a Linux Driver

[ 4] Device Drivers, Part 4: Linux Character Drivers

[ 5] Device Drivers, Part 5: Character Device Files — Creation & Operations

[ 6] Device Drivers, Part 6: Decoding Character Device File Operations

[ 7] Device Drivers, Part 7: Generic Hardware Access in Linux

[ 8] Device Drivers, Part 8: Accessing x86-Specific I/O-Mapped Hardware

[ 9] Device Drivers, Part 9: I/O Control in Linux

[10] Device Drivers, Part 10: Kernel-Space Debuggers in Linux  

[11] Device Drivers, Part 11: USB Drivers in Linux

[12] Device Drivers, Part 12: USB Drivers in Linux Continued

[13] Device Drivers, Part 13: Data Transfer to and from USB Devices

[14] Device Drivers, Part 14: A Dive Inside the Hard Disk for Understanding Partitions

[15] Device Drivers, Part 15: Disk on RAM — Playing with Block Drivers

[16] Device Drivers, Part 16: Kernel Window — Peeping through /proc

[17] Device Drivers, Part 17: Module Interactions  

[18] Writing a Linux Kernel Module — Part 1: Introduction

[19] Writing a Linux Kernel Module — Part 2: A Character Device

[20] Writing a Linux Kernel Module — Part 3: Buttons and LEDs