All about programming in GNU/LINUX

Linux Device drivers

Using Semaphores and Mutex in Linux Device drivers to tackle concurency

Semaphores provide a satisfactory solution for issues related to concurrency . Access to critical section is controlled by enforcing threads to hold a lock before entering the critical section , Without a semaphore being unlocked no thread is allowed access to execute in the critical section .

Semaphore is a single integer value bound to two functions P and V . P is called down or some variation of that name and V is called up . Function P is used to lock the critical region and V is used to unlock . Function P decreases the count of semaphores , for example from 1 to 0 . When the value of the semaphore still remains 0 or less no other threads are allowed to execute in the critical region . There are made to wait or even may be put to sleep till the region is unlocked .

Functions of type V releases the semaphores that’s being help , these category of functions increases the value count of the semaphore and may even wake up the processes waiting for the access into the critical region .

Semaphores are generally and mostly used for the purpose of mutual exclusion , that is to make sure that only one thread of execution or a process executes in the critical section .

 Linux Implementation of Semaphores

<asm/semaphore.h> defines several functionalities for semaphore and mutex implementation ( Mutexes are just a special case of semaphores ) .

To initialize a declared semaphore ,

void sema_init(struct semaphore *sema, int val) can be used ,

 this initializes the declared mutex to value val ( The second argument to the function ) ;

But , as i had mentioned earlier semaphores are generally used for implementation of mutex ,

the following two functions declare and initialize the mutex

DECLARE_MUTEX(mutex_name ) ;

DECLARE_MUTEX_LOCKED(mutex_name) ;

 These functions declares mutex named as ‘mutex_name’ , the function DECLARE_MUTEX declares the mutex and initializes it to 1 ( 1 is the unlocked state , a thread or a process can possibly lock the critical region ) , wherein the later function declares the mutex and initializes it to 0 ( 0 is the locked state ) , mutex has to be explicitly unlocked before it can be used for locking .

 

 For initializing the mutex at runtime the following functions can be used ,

void init_MUTEX(struct semaphore *sema ) ;

void init_MUTEX_LOCKED(struct semaphore *sema) ;

 Holding and releasing Mutex ( The version of P [down] and V[up] functions )

 Linux kernel provides three versions of down function

  1. void down(struct semaphore *sema) ;

  2. int down_interruptible(struct semaphore *sema) ;

  3. int down_trylock(struct semaphore *sema ) ;

As briefed earlier down decrements the value of the semaphore , successful exection of any version of down function leads to locking of the critical region of the code , and the thread of execution or the process is said to have acquired the lock . Other user space process trying to access the critical region of code will be made to wait .

In case of the first version of down ( void down_interruptible() ) , the user space process waiting for the mutex to be released cannot to interrupted , it’ll be running in a dreaded state . This is not desirable in many cases , so the second version of down allows the waiting user space process to be interrupted .

The third version can used to test and lock , if the lock is already being held the function returns with a negative value , If no other thread is holding the lock , the lock will be acquired . Using down_trylock() the process wont wait for the acquired lock to be released , it returns immediately .

void up(struct semaphore * sema) is used to unlock the acquired lock and even wake up the processes waiting on the semaphore .

When executing in the critical region on occurance of error its very important to unlock the region before returning

 

 

 

 

 

Advertisements

Concurrency issues in Linux Device Drivers and protection from concurrency

Concurrency was not a big issue till kernel support was introduced for SMP systems . Before there were only few sources of concurrency like the interrupt handling context , but with introduction of kernel support for SMP systems it also drastically increased the chances of  code being vulnerable for concurrency issues .SMP (Symmetric Multi Processing ) systems involve architecture with multiple processors , this introduces the possibility of the same code snippet being run in more than one processors at a given point of time . Codes running in kernel context are pre-emtible too , it may lose the processor at any point of time and the process scheduled next may run in your driver .There are surprisingly many ways where the Device Driver or a program running in kernel context becomes vulnerable for concurrency related issues , In the computing world where one in a million probable event too can happen every second bugs related to concurrency are difficult to identify even for expert kernel developers .

Reasons for concurrency

Consider this small code snippet in your driver

if(!dptr->data[block]) { //1.check whether memory is already allocated 
    dptr->data[block] = kmalloc(block_size , GFP_KERNEL); //2.allocate memory if its not allocated 
    if(!dptr->data[block] ) //3.check whether the memory allocation was successful 
         end the program ; 4.//exit if memory allocation fails 
}

Consider the case wherein processes A and B both try to access the driver . When both A and B arrives at the condition to check for memory allocation (line 1 ) , both predicts the memory to be not to be allocated and moves ahead executing the lines to follow allocating the memory .

The second process to allocate memory by executing kmalloc() wipes out the memory allocated by the first process . The second process gets the inconsistent view of the variable .

Such piece of code which is vulnerable for concurrency related bugs should be protected , such snippet of code is called as critical region of code . Critical region code should be protected from concurrent access , access to the critical region of code should be controlled and managed .

There are few rules following which the concurency related issues can be minimized and solved .Reduce the use of shared resources

  1. Hardware resources are shared and likewise many structures and variables too are accessible for any no.of.threads of execution , unless there is a strong reason its better not to make use of shared resoures ( like global variables ) .If there are no shared resources there wont be any concurrency issues , but sharing is often required and all varialbles in the driver by default is accessible all threads of execution .

  2. Use kernel’s locking primitives to manage and control access to critical region, if critical section of code is secured or prevented from multiple processes to execute simultaneously the issue can be solved .Kernel Provides different primitives to handle concurrency related issues since the triggering factor for all concurrency related issues are not same . Concurrency in the driver may be purely due to access from user space or code executed by asynchronous events like interrupt handling or kernel timer mechanisms . 

Ways of protecting critical section of code from concurrent access and kernel primitives and API’s that can be used in device drivers to protect the critical section of code will be discussed in the next post . Till then have great time coding ……….. 🙂


LIBUSB-1:Intro to LIBUSB , Writing and compiling the Hello World LIBUSB code in Ubuntu

Hello Friends ……..Recently a tiny bug in the Linux USB Device Driver had crashed my kernel , Its very common that bugs in the Device Drivers written brings down the system by crashing the kernel.The reason being that  unlike the usual programs we write Drivers run in kernel space.When programs run in user space and when we commit common programming snags like segmentation faults  or any invalid memory reference the kernel handles it to maintain the system stability , but in case of drivers which run in kernel space there is no one to monitor  since the kernel forms the core of the OS hierarchy .

Because of the above reasons its not safe to code in kernel space and better not unless there are no alternative ways to achieve the purpose.In driver writing , A simple unnoticed bug in a line of code could compromise the whole system.

Recently i came across a open source project by name LIBUSB . At first glance i assumed LIBUSB to be not so handy library to use and program with , but when i realized that using LIBUSB Linux USB Drivers can be written completely from user space i was amused to start using it to write USB Device Drivers under LINUX. 

What is LIBUSB ???

The official site  http://www.libusb.org/ quotes that “Libusb is a C library that gives applications easy access to USB devices on many different operating systems. libusb is an open source project, the code is licensed under the GNU LESSER GENERAL PUBLIC LICENSE or later.”

We’ll after realizing the importance of LIBUSB i was very curious on starting off with my first USB driver using LIBUSB libraries. So i installed LIBUSB libraries by issuing the following command on the terminal of my KUBUNTU machine

sudo apt-get install libusb-dev

we’ll the first of installing the LIBUSB library is over , now i wrote my HELLO WORLD libusb driver .

#include <stdio.h>
#include <stdlib.h>
#include <usb.h>
int main(){
    usb_init();
    printf("Hello world!\n");
    return 0;
}

Writing the hello world LIBUSB program was a piece of cake , a trivial task 😀 But the smile didnt remain longer since i couldnt find a way to compile the code 😦

During compiling the code using gcc/g++ the LIBUSB library had to be linked by using -L option . But i couldnt find the libusb libraries under /usr/lib where usually all the libraries reside.All the online resources i  referred stated the libraries to reside in /usr/lib .

Later i found out that the path where LIBUSB libraries (.a) and headers and their names differ with few versions of LIBUSB . So the best way out was to search where the current LIBUSB package installed its headers and library.

Since Debain based distributions (ubuntu , kubnutu , MINT ) use .deb packages which gets installed using the `dpkg` mechanism , i tried the following command to know the list of directories used by the libusb-dev package

dpkg -L libusb-dev

Now focus on the last few lines of the output and find the path where the library libusb.a and the header file usb.h/libusb.h is installed.

Image

Now make sure that you add the header name in the code and  , during compilation just add the parent directory of libusb.so along with the -L option of gcc/g++ .Also dont miss to add -lusb option during compilation.

gcc basiclusb.c -o basic -L/usr/lib/x86_64-linux-gnu/ -lusb

 

This succesfully compiles the Hello World LIBUSB code to give out the executable 😀 I hope this post would come pretty handy for those who want to start of with Writing USB device drivers in Linux using LIBUSB libraries.Catch u folks with more insights on wrting USB drivers under LINUX using LIBUSB in the posts to come 🙂

 

 

 


A simple Linux Driver : Allocating the Major Minor number

The first step in writing any Linux Device driver is to allocate a Major , Minor pair for the driver , this is a simple Linux Device Driver which allocates the Major , Minor pair and prints the same into DMESG


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>

static dev_t first;

static int __init my_init(void) /*constructor*/
{
    int ret;

    printk(KERN_INFO "Drivre is being registered");
    if((ret = alloc_chrdev_region(&first,0,3,"Rao")) < 0) /*dynamic allocatin of Major , minor numbers*/
    {

        return ret;
    }
    printk(KERN_INFO "<MAJOR , MINOR ><%d %d>\n" , MAJOR(first),MINOR(first)); /*printing the Major , Minor pair into the dmesg*/
    return 0;
}

static void __exit my_exit(void) /*destructor function , executed when the driver is removed*/
{
    unregister_chrdev_region(first,3); 
    printk(KERN_INFO "dRIVER REMOVED");
}

    module_init(my_init);
    modue_exit(my_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("KARTHIC RAO");
    MODULE_DESCRIPTION("A SIMPLE DRIVER OF MINE");

here is the MAKEFILE for the driver

# If called directly from the command line, invoke the kernel build system.
ifeq ($(KERNELRELEASE),)

	KERNEL_SOURCE := /usr/src/linux-headers-$(shell uname -r)
	PWD := $(shell pwd)
default:
	$(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) modules

clean:
	$(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) clean

# Otherwise KERNELRELEASE is defined; we've been invoked from the
# kernel build system and can use its language.
else

	obj-m := simple_driver.o

endif

Linux-Device-Driver-5:Dynamic Major number allocation for the Driver

 
The Linux kernel Identifies a driver by its <major,monor> number pair .Here is the listing of /dev  Image

The fifth column corresponds to the major number of the devices and the 6th row correponds to the minor number of the device.

The same major number and can assigned to another driver but it will have a different minor number .
There is always a possibility that the major number assigned to a driver already exist and is being by another driver.
To prevent this its always better to dynamically assign the Major nubmer to a  driver and let the kernel allocate a Major number which is freely available at the time of driver registration .
This is achieved by using the following call

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

1.The first parameter is the pointer to a variable of data type dev_t , the call fills the variable with the value of the new dynamically assigned major number .
2.The second parameter the starting Minor number to be paired up along with the major number and the 3rd parameter is the total number of minor number required to be assingned starting from the minor nubmer specified in the second argument (second parameter) to the call .
3.Any Device Driver in Linux which is assigned a Major number by the kernel will be listed by the kernel in the file /proc/devices  with name of the device and its Major Number .The fourth argument is the name with which the device will be listed in the file /proc/devices .
Image
unregister_chrdev_region(Dev_t Dev,unsigned int count); is used to unregister the registered device .


Linux Device Drivers-4 : The V.F.S Connection

 To make use of a character device driver , the associated character device file (usually located under /dev) is to be used .Using the following command gives you the list of all character device files in /dev

 ls -l /dev | grep “^c”

Image

 For any user-level application to access the underlying hardware , it should make use of the device driver corresponding to the device . But communication with the device driver can only be made by performing file operations on the device file corresponding to the device (usually located under /dev).

Therefore for any user-level application to get services out of a hardware , it should perform file operations on the corresponding device files .

 The character device driver and the its corresponding device file are connected togetby V.F.S(Virtual File system) . The functions implemented inside the driver will perform her the low-level file access into the underlying hardware .

 From Linux kernel 2.6 onwards there can exist multiple drivers under same MAJOR.NO

 The connection of the device file with the driver is done by means of its <MAJOR.NO,MINOR.NO> pair .This connection is established by using the following to API’s in the driver .These calls registers a <MAJOR,MINOR> pair for the driver. Later any device file created by assigning the same major , minor number will be linked to the corresponding driver .

 int register_chrdev_region(dev_t first,unsigned int no_of_minors,char *name);

 This call associates the driver with the <Major,MINOR> pair stored in the variable first.The variable no_of_minors specifies the no.of.minor numbers to be registered .

 Sometimes the Major number requested may be already allocated and may not be free.So its better to dynamically allocate the MAJOR number and let kernel search a free MAJOR number and assign the same .The following function is used to achieve the purpose .

 int alloc_chrdev_region(dev_t *first,unsigned int first_minor,unsigned int no_of_minors,char *name);

 When any file operations are performed on a device file , it is passed onto the driver my V.F.S .

To achieve this purpose the corresponding functions to be invoked by the driver(functions written inside the driver code) when different file operations* performed on the device file have to be registered with the V.F.S.

This registration is first done by initializing the structure file_operations and then handing the structure

to V.F.S by using the function cdev_add which is declared under <linux/cdev.h>

refer:http://www.linuxforu.com/2011/04/character-device-files-creation-operations/


Linux device Drivers -3:The layers

The Driver is just the part of the flow or the linkage from an application to the low-level access .
This linkage involves four parts
1)The Application
2)The Device file (usually created under /dev)
4)The Device Driver
4)The Hardware device and Low/level Device access

Image

A Device may not always have a physical existance ex:/dev/null , a Device could just have a logical existance.For such a logical device the fourth part of the flow ,i.e the Hardware Device and Low/level Device access doesnt exist.

An explicit connection has to be established between adjacent layers(the 4 parts) of the flow to have an complete Application which could access the underlying hardware and get the job done.That is an explicit connection has to estalished b/w
A)The Application and the Device Device
  -This is achieved by reading or writing into the device file under /dev.The application program could execute system calls to connect these two layers .

B)The Device file and The Device Driver
 -This is achieved by registrations done by the device driver for the Major and Minor number s of the device .The device driver uses kernel space API’s to achieve this.

C)The Device Driver and The Hardware Device
 -The Driver uses low-level kernel space API’s to achieve this .

For a Device like /dev/null which just has a logical existance the part C(The link b/w the Driver and the hardware device) of the connection doesnt exist.