How to develop simple Linux Character driver

In Linux, the character devices are used for transferring a small amount of data from user space application to Hardware device, similarly, this interface is also used to receive data from Hardware device to userspace application. In this article we develop a simple Linux character driver for Turning on LED connected to Raspberry-Pi GPIO, and also read the message from driver to userspace applications.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#include <linux/gpio.h>

MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("c2plabs.com");
MODULE_DESCRIPTION("Simple Character device example");
MODULE_VERSION ("1.0");

#define DEV_NAME "simdev"
#define  CLASS_NAME "sim"

#define LED_PIN 23

static struct device *simCharDev = NULL;
static struct class *simCharClass =NULL;
static int major =0 ;
static char Device_read_data[]="This is test data send to app on read device";
static char Device_write_data[256] = {0x00};

static int char_dev_open (struct inode *inode_p, struct file *file_p );
static int char_dev_close (struct inode *inode_p, struct file *file_p);
static ssize_t char_dev_read (struct file * file_p, char *buff, size_t len, loff_t *offset);
static ssize_t char_dev_write(struct file * file_p, const char *buff, size_t len, loff_t *offset);

static int char_dev_init (void)
{
	printk (KERN_INFO "This is init for simple char driver \n");
	
	major = register_chrdev(0, DEV_NAME, &char_dev_fops);
	if (major < 0) {
		
	printk (KERN_INFO "Major number allocation failed  %d ",major);
	return major;
	}
	simCharClass = class_create(THIS_MODULE,CLASS_NAME);
	if ( IS_ERR(simCharClass) ) {

		unregister_chrdev(major,DEV_NAME);
		return -1;
	}
	
	simCharDev = device_create (simCharClass, NULL, MKDEV (major,0),NULL, DEV_NAME);
	
	if ( IS_ERR(simCharDev) ) {
	
		unregister_chrdev(major,DEV_NAME);
		return -1;

	}

	gpio_request (LED_PIN,"led_pin");
	
	printk (KERN_INFO "Device registration successful with Major num %d",major);	
	return 0;
}

In Linux, character devices are accessed using device files in /dev/. These device files are associated with Major and Minor numbers.

bash#ls -la /dev/simdev

crw------- 1 root root 251, 0 Jun  2 18:26 /dev/simdev

Above command shows /dev/simdev is a character device with Major number 251 and Minor number zero. All the devices using or controlled by the same driver (device class) have the same Major number. Whereas minor number indicates the number of devices of using a particular type.

register_chrdev () kernel call is used for registering character device. This takes three arguments Major number, Name of the device and very important file_operations structure. If zero is given as Major number, Linux kernel assign Major number dynamically and return by register_chrdev() kernel call.

major = register_chrdev(0, DEV_NAME, &char_dev_fops);

static struct file_operations char_dev_fops = { 
		.open = char_dev_open,
		.read = char_dev_read,
		.write = char_dev_write,
		.release = char_dev_close,
		};

the file_operations structure is defined in Linux kernel headers in linux/fs.h. The members of this structure are the pointers to the functions defined by the character driver. These functions will be executed when corresponding system calls are called from the userspace application. For the system calls to which function pointer is not defined they are initialized with NULL.

class_create() and device_create() calls are used to create device node in userspace /dev/ directory using udev.  We will see how udev works in later posts in detail.

static ssize_t char_dev_read (struct file * file_p, char *buff, size_t len, loff_t *offset)
{
	int ret=0;
	ret = copy_to_user(buff, Device_read_data, sizeof(Device_read_data));
	
	if (ret == 0) {
		printk (KERN_INFO "Successfully send read dev message \n");
	}
	else 
		printk (KERN_INFO " %d byetes are not sent to userspace \n",ret);
	
	return sizeof(Device_read_data);
}

static ssize_t char_dev_write(struct file * file_p, const char *buff, size_t len, loff_t *offset)
{
	int ret = 0;
	if (unlikely(buff == NULL)) {
		printk (KERN_INFO "Device write error \n");
		return -1;
	}

	ret = copy_from_user (Device_write_data,buff,len);
	if (ret == 0) {
		printk (KERN_INFO "Successfully copied dev write message \n");
		if (strcmp(Device_write_data,"LED_ON")==0) {
		printk (KERN_INFO "Data from app is: %s \n",Device_write_data);	
		gpio_direction_output(LED_PIN,0);
		}
		else if (strcmp (Device_write_data,"LED_OFF")==0) {
		printk (KERN_INFO "Data from app is: %s \n",Device_write_data);
		gpio_direction_output(LED_PIN,1);
		}
		
	}
	else {
		printk (KERN_INFO "%d butes are copied from write data \n",((int)len - ret));
	}
	
	return 0;
}

We look at two important members of the file_operations structure, read and write system calls, these char_dev_read and char_dev_write functions are assigned to .read and .write function pointers.

In the char_dev_read function, we copy the Kernel data to userspace application using copy_to_user kernel call. char_dev_read returns the no of bytes copied to userspace. This is the return value for the read system call.

In the char_dev_write function, we get the data from a userspace application and check the content string received from userspace application, and set/clear GPIO for triggering LED.  Accessing GPIO is used in the write system call to showcase how the kernel driver can be used to access the Hardware.

#include <stdio.h>
#include <fcntl.h>


#define DEV_NAME "/dev/simdev"

int main ()
{
	int fd;
	int ret = 0;
	char read_buff[256] = {0x00};
	char write_buff_on[] ="LED_ON";
	char write_buff_off[]="LED_OFF";	
	int cnt = 0;

	fd = open (DEV_NAME,O_RDWR);
	
	if (fd < 0) {
		fprintf (stderr,"Device Open failed \n");
		return -1;
	}

	ret = read (fd,read_buff,25);
	
	if (ret > 0) {

		fprintf (stderr, "Msg read from Device %s \n",read_buff);
	}


	while (cnt++ < 10)
	{
		ret = write (fd,write_buff_on,sizeof(write_buff_on));
	
		if (ret > 0) {
			fprintf (stderr, "Message successfully written \n");

		}
		sleep (1);
                
		ret = write (fd,write_buff_off,sizeof(write_buff_off));
                if (ret > 0) {
                        fprintf (stderr, "Message successfully written \n");

                }

		sleep(1);
		
	}

	close (fd);
	return 0;
}

 

 

 

 

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *