A Basic Character Driver — to read and write messages

I will discuss a basic character driver, which can read last character and display. It is a simple driver one can develop much better driver based upon this..

For any character driver there must exists a file under /dev directory, this file can be automatically created by using appropriate Kernel APIs such as class_create, device_create, class_destroy, device_destroy prototyped under #include <linux/device.h>. But here I will create them manually for the sake of showing simple and basic driver source code.

$ sudo mknod /dev/dummy_memory c 60 0

and make it read and writable by everyone

$ sudo chmod 666 /dev/dummy_memory

Here major and minor numbers are selected by myself, but this kind of programming is a bad practice, try to achieve this using respective APIs which I discussed earlier.

Start writing

Open any of your favorite editor, where as mine is vim

$ vim dummy_memory.c

Needed for any Kernel Module,

$ #include <linux/module.h>

Needed for KERN_INFO, KERN_ALERT kind of macros,

$ #include <linux/kernel.h>

Needed for matching versions,

$ #include <linux/version.h>

Global Declaration of struct file_operations

Virtual File System lies in Kernel space just above character driver and low-level I/F. This VFS decodes the file type and transfers the file operations to appropriate device driver. Now for VFS to pass the device file operations onto the driver, it should have been informed about it. That is what we call as registering the file operations by driver with VFS. Basically it involves by using file_operations structure, which holds appropriate functions to be performed during usage.

static struct file_operations memory_fops = {.owner = THIS_MODULE,.open = memory_open,.release = memory_release,.read = memory_read,.write = memory_write,};

And also I declared two more global variables for my usage,

static int memory_major = 60;static char *memory_buffer;module_init(memory_init);static int __init memory_init(void) {
int result;
result = register_chrdev(memory_major,”dummy_memory”,&memory_fops);if(result < 0) {
printk(KERN_INFO “<1> dummy_memory : Cannot obtain major number %d\n”,memory_major);
return result;
}
memory_buffer = kmalloc(1,GFP_KERNEL);if(!memory_buffer) {
result = -ENOMEM;
goto fail;
}
printk(KERN_INFO “<1>Inserting memory module\n”);return 0;fail:
memory_exit();
return result;
}

Here two things to be noted,

1) register_chrdev function

Within the driver, in order to link it with its corresponding /dev file in kernel space, the register_chrdev function is used. It is called with three arguments: major number, a string of characters showing the module name, and a file_operations structure which links the call with the file functions it defines. FYI register_chrdev and deregister_chrdev is prototyped under #include <linux/fs.h>

2) kmalloc

Also, note the use of the kmalloc function. This function is used for memory allocation of the buffer in the device driver which resides in kernel space. Its use is very similar to the well known malloc function. Refer Kernel API for more info. FYI kmalloc and kfree is prototyped under #include <linux/slab.h>

Finally, if registering the major number or allocating the memory fails, the module acts accordingly.

module_exit(memory_exit);static void __exit memory_exit(void) {
unregister_chrdev(memory_major,”dummy_memory”); // Freeing Major number
if(memory_buffer)
kfree(memory_buffer); // Freeing buffer memory
printk(KERN_INFO “<1>Removing memory module\n”);
}

It is reverse of previous, acts vice versa.

Opening device as file

The kernel space function, which corresponds to opening a file, is the member .open = my_open of the file_operations structure in the call to register_chrdev. In this case, it is the memory_open function. It takes as arguments: an inode structure, which sends information to the kernel regarding the major number and minor number; and a file structure with information relative to the different operations that can be performed on a file.

Neither of these functions will be covered in depth within this article.

When a file is opened, it’s normally necessary to initialize driver variables or reset the device. In this simple example, though, these operations are not performed.

static int memory_open(struct inode *i,struct file *f) {
return 0; // success
}

Closing device as file

The corresponding function for closing a file, is the member .release = memory_release of the file_operations structure in the call to register_chrdev. In this particular case, it is the function memory_release, which has as arguments an inode structure and a file structure, just like before. When a file is closed, it’s usually necessary to free the used memory and any variables related to the opening of the device.

But, once again, due to the simplicity of this example, none of these operations are performed.

static int memory_release(struct inode *i,struct file *f) {
return 0; // success
}

Reading the device

To read a device, the member .read = memory_read of the file_operations structure is used in the call to register_chrdev. This time, it is the function memory_read. Its
arguments are: a type file structure; a buffer (buf), from which the user space function will read; a counter with the number of bytes to transfer (count), which has the same value as the usual counter in the user space function; and finally, the position of where to start reading the file (f_pos).

In this simple case, the memory_read function transfers a single byte from the driver buffer
(memory_buffer) to user space with the function copy_to_user which is prototyped under #include <asm/uaccess.h>

static ssize_t memory_read(struct file *f,char *buf,size_t count,loff_t *f_pos) {
copy_to_user(buf,memory_buffer,1);
if(*f_pos == 0)
{
*f_pos += 1;
return 1;
}
else
return 0;
}

Writing the device

To write to a device, member .write = memory_write of the
file_operations structure is used in the call to register_chrdev. It is the function memory_write, in this particular example, which has the following as arguments: a type file structure; buf, a buffer in which the user space function (fwrite) will write; count, a counter with the number of bytes to transfer, which has the same values as the usual counter in the user space function (fwrite); and finally, f_pos, the position of where to start writing in the file.

In this case, the function copy_from_user which is prototyped under #include <asm/uaccess.h> transfers the data from user space to kernel space.

static ssize_t memory_write(struct file *f,char *buf,size_t count,loff_t *f_pos) {
char *tmp;
tmp = buf + count — 1;copy_from_user(memory_buffer,tmp,1);return 1;
}

At last, my driver will look a like,

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h> // prototype for register_chrdev() and unregister_chrdev()
#include <linux/slab.h> // kmalloc and kfree
#include <asm/uaccess.h> // copy_to_user and copy_from_userint memory_major = 60;char *memory_buffer;static int memory_open(struct inode *i,struct file *f)
{
return 0; // success
}
static int memory_release(struct inode *i,struct file *f)
{
return 0; // success
}
static ssize_t memory_read(struct file *f,char *buf,size_t count,loff_t *f_pos)
{
copy_to_user(buf,memory_buffer,1);
if(*f_pos == 0)
{
*f_pos += 1;
return 1;
}
else
return 0;
}
static ssize_t memory_write(struct file *f,char *buf,size_t count,loff_t *f_pos)
{
char *tmp;
tmp = buf + count — 1;copy_from_user(memory_buffer,tmp,1);return 1;
}
static struct file_operations memory_fops =
{
.owner = THIS_MODULE,
.open = memory_open,
.release = memory_release,
.read = memory_read,
.write = memory_write,
};
static void __exit memory_exit(void)
{
unregister_chrdev(memory_major,”dummy_memory”); // Freeing Major number
if(memory_buffer)
kfree(memory_buffer); // Freeing buffer memory
printk(KERN_INFO “<1>Removing memory module\n”);
}
static int __init memory_init(void)
{
int result;
result = register_chrdev(memory_major,”dummy_memory”,&memory_fops);if(result < 0)
{
printk(KERN_INFO “<1> dummy_memory : Cannot obtain major number %d\n”,memory_major);
return result;
}
memory_buffer = kmalloc(1,GFP_KERNEL);if(!memory_buffer)
{
result = -ENOMEM;
goto fail;
}
printk(KERN_INFO “<1>Inserting memory module\n”);return 0;fail:
memory_exit();
return result;
}
module_init(memory_init);
module_exit(memory_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“S MOHAMMED AUDHIL”);
MODULE_DESCRIPTION(“DUMMY MODULE to read and write /dev/dummy_memory file”);

I created a simple Makefile

obj-m += dummy_memory.oKDIR := /usr/src/linux-headers-$(shell uname -r)PWD := $(shell pwd)defaults:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

Compiling

$ make

Output can be seen as follows,

Image

and dmesg

Image

Enjoy..!

--

--