Thursday, August 20, 2015

Linux kernel module programming


1. Introduction

         This chapter explains the basic elements for writing a kernel module. By end of this chapter readers can

             * Write a kernel module
             * Pass arguments to kernel module
             * Export from kernel modules       

For executing the sample codes, I suggest to use ubuntu machine with Linux kernel version > 2.6      

Note ::

All examples in this post are executed and tested with     

1.1 Simplest kernel module

#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */

MODULE_LICENSE("GPL");    /* Setting Licence to GPL */

int __init ourinitmodule(void) /* Will be called during insmod <module>.ko */
{

    printk(KERN_ALERT "\n Welcome to sample kernel module.... \n");

    return 0;
}

void __exit ourcleanupmodule(void) /* Will be called during rmmod <module> */{
    printk(KERN_ALERT "\n Thanks....Exiting. \n");
    return;
}
/* Macros for init and cleanup module */
module_init(ourinitmodule);
module_exit(ourcleanupmodule);

Refer the following github example for ready-to-run code.
https://github.com/jeyaramvrp/kernel-module-programming/tree/master/helloworld

1.2  Passing command line arguments to a kernel module

#include <linux/module.h>
#include <linux/kernel.h>
/* Added for module_param */
#include <linux/moduleparam.h>

MODULE_LICENSE("GPL");

/* Module param : Integer data */
static int data;
module_param(data, int, S_IRUSR|S_IWUSR);

/* Module param : String data */
static char *mystr = "Default";
module_param(mystr, charp, 0);

/* Module param : Integer Array data */
static int myarray[10] = {-1};
static int count = 0;
module_param_array(myarray, int,&count, 0);

int ourinitmodule(void)
{
    int tmp = 0;

    /* Print data - Default values will be printed if No argument is passed. */
    printk(KERN_ALERT "\n Demo for Passing arguments to kernel module \n");
    printk(KERN_ALERT "\n data:%d\n", data);
    printk(KERN_ALERT "\n mystr:%s\n", mystr);

    for(tmp = 0; tmp < count ; tmp++)   
    printk(KERN_ALERT "\n myarray[%d]:%d", tmp, myarray[tmp]);

    return 0;
}

void ourcleanupmodule(void)
{
    printk(KERN_ALERT "\n Thanks....Exiting Passing args sample.. \n");
}

module_init(ourinitmodule);
module_exit(ourcleanupmodule);
http://stackoverflow.com/questions/10994576/passing-an-array-as-command-line-argument-for-linux-kernel-module

https://github.com/jeyaramvrp/kernel-module-programming/tree/master/passingargs

http://lxr.free-electrons.com/source/include/linux/moduleparam.h?a=arm#L112

1.3  Exporting a function to another kernel module(Module dependency) 


#include <linux/module.h>
#include <linux/kernel.h>

#include "CommonHeader.h"

MODULE_LICENSE("GPL");

int __init ourinitmodule(void)
{
    printk(KERN_ALERT "\n sample -1 init.... \n");
    return 0;
}

void __exit ourcleanupmodule(void)
{
    printk(KERN_ALERT "\n sample -1 Exit.... \n");
}

int sample1func()
{
    printk(KERN_ALERT "\n sample -1 Exported Function .... \n");
    return 0;
}
EXPORT_SYMBOL(sample1func);

module_init(ourinitmodule);
module_exit(ourcleanupmodule);
In the above kernel module(module - 1), samplefunc() is exported by this module. i.e It can be used by other kernel modules. For exporting a function/global variable
        
            1. It should not be "static"
            2. Explicitly exported using EXPORT_SYMBOL() macro

There are other variants of EXPORT_SYMBOL(). It is up to reader's interest.
http://lxr.free-electrons.com/source/include/linux/export.h#L68

Lets see how other kernel module(module -2) uses the exported samplefunc()

#include <linux/module.h>
#include <linux/kernel.h>

#include "CommonHeader.h"

MODULE_LICENSE("GPL");

int __init ourinitmodule(void)
{
    printk(KERN_ALERT "\n sample -2 init.... \n");
    /* Making two.c to depend on one.c */
    sample1func();
    return 0;
}

void __exit ourcleanupmodule(void)
{
    printk(KERN_ALERT "\n sample -2 Exit.... \n");
}

module_init(ourinitmodule);
module_exit(ourcleanupmodule);
Refer here for full code & Make files.
https://github.com/jeyaramvrp/kernel-module-programming/tree/master/depmod-export-sym-demo

module-2 which uses samplefunc() requires module-1 loaded already. Refer the following screenshot which explains the sequence of operation.


2. Device Driver Programming

        This chapter guides a kernel programmer to understand the device driver especially char device driver. By end of this chapter readers will have understanding of 

             * Writing a char device driver
             * Using char driver at user space

2.1  What is a device driver ?


       Device drivers are kernel modules specifically written to handle particular hardware. Since the kernel is responsible managing the hardware, piece of code is written to handle it. 

The Linux kernel provides APIs for writing the device driver so that the device seamlessly integrated in to our system. If the particular device is vendor specific(i.e Not available in market), mostly it needs driver to be written. Otherwise Linux kernel itself has lot of drivers for most of the devices available in market today.  

To make the kernel generic as much as possible, built-in drivers which is already written by kernel community is controlled by configurations. 

2.2  What are all the major types of device driver?

       The Linux kernel drivers can be categorized in to two major types
                       
                     * Character device driver
                     * Block device driver
    
Char device drivers usually deals with read/write byte on device. For example keyboard or mouse drivers. There is no much involvement of large data to handle unlike hard disk.

Block devices usually involves large data to handle such as hard disk, SD card.

The sources for character devices are kept in drivers/char/, and the sources for block devices are kept in drivers/block/. They have similar interfaces, and are very much alike, except for reading and writing. Block devices will be covered later.
   
2.3  Character device driver

       This section will explain Char device drivers with a sample.  

2.3.1 Major and Minor Number 
       
       The Linux kernel refers each driver with a number called Major number. This major number is either allocated by kernel or specified by driver developer. While the developer specifies the major number, he should be careful enough to use unused number.


Refer http://lxr.free-electrons.com/source/include/uapi/linux/major.h for already allocated major numbers.

Minor numbers represents the devices. For example, if two devices of same type is connected to system(say two MS USB mouse), then each device will be assigned a number. But these two devices will be handled by single driver.

Lets see the following screen for clearing understanding of Minor and Major number.


The Linux kernel assigns 4 as the Major number for TTY driver(Refer Here). 
So each tty device is assigned with unique minor number.

Did you notice each row starts with "c" ??. This is meant for char driver. For block driver it will be "b".

2.3.2 file_operations structure

The file_operations structure is define in <linux/fs.h>. This structure is a collection of function pointers which is implemented in our drivers.

In the following driver example, only minimal operations are implemented. Just have a look at how the file operation is filled. At the end of example, you will get idea of file_operations struct.

static const struct file_operations sample_fops = {
        .owner = THIS_MODULE,
        .unlocked_ioctl = sample_ioctl,
        .open           = sample_open,
        .release        = sample_close 
};  
2.3.3 Register and un-register device driver
The Linux kernel provides APIs for register and un register of your driver. In this registration, driver obtains major number and the list of operations supported by driver will be intimated to kernel.


major = register_chrdev(0, "samplechar", &sample_fops);
The first argument as 0 allows the kernel to allocate a major number which is available. If you want specific number, pass here instead of 0. If that number is not allocated already then it will be assigned to this driver. Best practice is to pass 0.

Second argument represents the driver name which is displayed in /proc/devices

Third argument is the pointer to file_operations structure which we filled earlier. 

Usually this registration is done at the init function of kernel module(i.e Driver's init module)

The un register operation is done at clean up function.
unregister_chrdev(major, "samplechar");
The major number and driver name is need for un register.
2.3.4 ioctl()
ioctl() is very important function in device driver.  The commands specific to devices are issued to driver through ioctls. From user space, the command is received at ioctl() and the corresponding action will be taken by driver.

Lets write a calculator with device driver APIs to demonstrate. The ioctl() function will be similar to 


static long sample_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    switch(cmd) 
        {
        case SAMPLE_IOCTL_ADD:
        {
            // Addition related operation   
            return 0;
        }

        case SAMPLE_IOCTL_SUB:
        {
            // Subtraction related operation   
            return 0;
        }

        case SAMPLE_IOCTL_MUL:
        {
           //Multiplication related operation
            return 0;
        }

        case SAMPLE_IOCTL_DIV:
        {
           //Division related operation
            return 0;
        }
        default:
            return -EINVAL;
    }
}
Based on the ioctl command from user space, driver performs operations and returns results to user space if necessary. The functions copy_to_user() and copy_from_user() is responsible for copying the information to/from user space.
2.3.5 Sample driver with user space application 
Refer here for the sample char driver which implements basic calculator functionality.

After compiling the driver, insert in to kernel by using insmod.

# insmod sampledrv.ko

Create the device node of type "c" with major number(say 58) in /dev 

#mknod /dev/samplechar c 58 1

Now driver is ready to be used by applications. Lets write application which uses this calc driver.



#include <stdio.h> #include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "../sampleioctl.h"
int main()
{
struct calc dat;
int opt;
int fd, err=0;
fd = open("/dev/samplechar", O_RDWR);
if(fd < 0)
{
perror("open:");
return -1;
}
while(1)
{
printf("\n 1.Add\n2.Sub\n3.Mul\n4.Div\n \
5.ExitApp\nChoose Operation:" );
scanf("%d", &opt);
if(opt == 5)
break;
if(opt < 1 || opt > 4)
{
printf("\n Invalid Option... ");
continue;
}
printf("\n Enter Data1:");
scanf("%d", &dat.data1);
printf("\n Enter Data2:");
scanf("%d", &dat.data2);
switch(opt)
{
case 1:
err=ioctl(fd, SAMPLE_IOCTL_ADD, &dat);
break;
case 2:
err=ioctl(fd, SAMPLE_IOCTL_SUB, &dat);
break;
case 3:
err=ioctl(fd, SAMPLE_IOCTL_MUL, &dat);
break;
case 4:
err= ioctl(fd, SAMPLE_IOCTL_DIV, &dat);
break;
}
printf("\n Result is :%d", dat.result);
}
close(fd);
return 0;
}
   

Based on user's selection, the application fills the operands in structure and send to kernel for processing through ioctl(). Since the sample driver is registered for that device node, the ioctl() in driver will be invoked.

The ioctl() in kernel performs the corresponding operation and returns the result to the user space application. 

2.3.6 Wrappers for drivers in user space  
Usually driver developers will hide the complexity of opening device file, remembering the ioctl commands. If the application is developed by third party, it is better to write a wrapper which exposes only functionality.

The wrapper is written as shared library(.so) which takes care of handling driver related stuff and exposes only functionality.

The following is the source code for shard library.



#include <stdio.h> #include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include "../sampleioctl.h"
#include "mylib.h"
int fd;
struct calc dat;
int OpenDev()
{
fd = open("/dev/samplechar", O_RDWR);
if(fd < 0)
{
perror("open:");
return -1;
}
return 0;
}
int Add(int data1, int data2)
{
int err=0;
dat.data1 = data1;
dat.data2 = data2;
err=ioctl(fd, SAMPLE_IOCTL_ADD, &dat);
if(err < 0)
{
perror("ioctl:");
return -1;
}
return dat.result;
}
int Sub(int data1, int data2)
{
int err=0;
dat.data1 = data1;
dat.data2 = data2;
err=ioctl(fd, SAMPLE_IOCTL_SUB, &dat);
if(err < 0)
{
perror("ioctl:");
return -1;
}
return dat.result;
}
int Mul(int data1, int data2)
{
int err=0;
dat.data1 = data1;
dat.data2 = data2;
err=ioctl(fd, SAMPLE_IOCTL_MUL, &dat);if(err < 0)
{
perror("ioctl:");
return -1;
}
return dat.result;
}
int Div(int data1, int data2)
{
int err=0;
dat.data1 = data1;
dat.data2 = data2;
err= ioctl(fd, SAMPLE_IOCTL_DIV, &dat);
if(err < 0)
{
perror("ioctl:");
return -1;
}
return dat.result;
}
int CloseDev()
{
close(fd);
return 0;
}
        
Use the following commands to generate the shared library.
gcc -fPIC -g -c -Wall mylib.c
gcc -shared -W1,-soname, -o libmylib.so mylib.o -lc
Then link this library with the application.
Here is the sample application which uses this library.



#include <stdio.h> #include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "../mylib.h"
int main()
{
int data1, data2;
int opt;
int err=0;
err = OpenDev();
if(err < 0)
{
printf("\n open: Error in file open... ");
return -1;
}
while(1)
{
printf("\n 1.Add\n2.Sub\n3.Mul\n4.Div\n \
5.ExitApp\nChoose Operation:" );
scanf("%d", &opt);
if(opt == 5)
break;
if(opt < 1 || opt > 4)
{
printf("\n Invalid Option... ");
continue;
}
printf("\n Enter Data1:");
scanf("%d", &data1);
printf("\n Enter Data2:");
scanf("%d", &data2);
switch(opt)
{
case 1:
err = Add(data1, data2);
break;
case 2:
err=Sub(data1, data2);
break;
case 3:
err= Mul(data1, data2);
break;
case 4:
err= Div(data1, data2);
break;
}
printf("\n Result is :%d", err);
}
CloseDev();
return 0;
}

Just compare the app with wrapper and with out wrapper. This gives you some clear understanding of creating wrapper for drivers.

Here is the git hub which provides all necessary make files and source/header for execution & testing.








11 comments:

  1. This is really an amazing post, thanks for sharing such a valuable information with us,
    DevOps Training
    DevOps Classroom Training in Hyderabad

    ReplyDelete


  2. It was great experience after reading this. thanks for sharing such good stuff with us.
    Linux Training in Delhi

    ReplyDelete
  3. Great, thanks for sharing this post.Much thanks again. Awesome.
    Data Science training
    linux training

    ReplyDelete
  4. Wondering how to fix Pinnacle Game Profiler not opening on Windows 10? Try the fixes like update the Pinnacle game profiler or run as admin.Pinnacle Game Profiler Crashes

    ReplyDelete
  5. Synthesia Key is the full latest version is connected each point run working main user keyboard have to be show on the screen in application. Synthesia Full Version

    ReplyDelete
  6. You are spreading a Knowledge . it help a people to Know about embeded system


    JTAG

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Impressive Article. Thanks for sharing.
    Azure DevOps Training Online
    Azure DevOps Online Training
    Azure DevOps Online Training in Hyderabad
    Azure DevOps Course Online
    Microsoft Azure DevOps Online Training
    Azure DevOps Training in Hyderabad
    Azure DevOps Training
    Azure DevOps Training in Ameerpet

    ReplyDelete
  9. At APTRON Solutions, we believe in learning by doing. Our Linux Course in Noida provides ample opportunities for hands-on practice in a simulated environment. You'll work on real-world projects and assignments, allowing you to apply theoretical concepts to practical scenarios. Our experienced trainers offer personalized guidance and support, ensuring that you make the most out of your learning journey.

    ReplyDelete