Linux Device Driver

Youtube video Link

https://youtu.be/Qw5DuYUncwg



Pre-Requested 

1. Basic of C Programing

2. Basic of Unix Command and Terminal 

3. Any Linux Distribution (Ubuntu recommended for getting started) 

Module piece of code that can be added to the kernel at runtime is called a module.

Each module usually implements one of these types, and thus is classifiable as a

char module, a block module, or a network module.

A character (char) device is one that can be accessed as a stream of bytes (like a

file) a char driver is in charge of implementing this behavior. Such a driver usually

implements at least the open, close, read, and write system calls.

A block device is a device (e.g., a disk) that can host a filesystem. In

most Unix systems, a block device can only handle I/O operations that transfer

one or more whole blocks, which are usually 512 bytes (or a larger power of

two) bytes in length.

A network interface is in charge of sending and receiving data packets, driven by

the network subsystem of the kernel, without knowing how individual transactions

map to the actual packets being transmitted.

Setting Up Environment

$ sudo apt-get install linux-headers-$(sh -c "uname -r")

(mine is 5.8.0-43-generic) is subject to change

/* main.c */
#include<linux/module.h>
static int __init helloWorld_init(void)
{
	pr_info("Hello World\n");
	return 0;
}

static void __exit HelloWorld_exit(void)
{
	pr_info("GoodBye World\n");
}

module_init(helloWorld_init);
module_exit(HelloWorld_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("pmvanker");
MODULE_INFO(board,"Beaglebone Black");

$ make

$ sudo insmod main.ko

$ dmesg | tail


Linux Kernel Module Special Macros 

module_init() , module_exit() this macro register the function in kernel space.

__init for initialization function which is called once then removed from kernel space.

__init also put the code in .init section of the final ELF of linux kernel image

__exit for cleanup the module . exactly reverse of __init will free memory

if static module( this will be not part of the kernel image)

other macro like MODULE_LICENSE are used for module specific data

There is Two type of Module

1 Static Module

2 Dynamic Module

also other classification are below

1 In tree Module (internal to kernel)

2 Out of Tree Module (not internal to kernel)

Makefile

obj-<x>=<module-name>.o

x = n ; do not compile module

x = y ; compile kernel and link with kernel image

x = m ; compile as dynamically loadable kernel module

VFS (Virtual File System)

connect the system call (open, read, write …) with driver call.

Device Number

When we call SYS call from user-space , how kernel identify which driver to connect? for that kernel uses device number

there are multiple device can be which use same driver. so device driver having two numbers

Major Number refer to driver

Minor Number refer to device 

Kernel Header Filese

/include/linux/fs.h 

alloc_chrdev_region(), unregister_chrdev_region()

/include/linux/cdev.h

cdev_init(), cdev_add(), cdev_del()

/inlcude/linux/device.h

device_create() device_destory() class_create() class_destroy()

/inlcude/linux//uaccess.h

copy_to_user() copy_form_user()


Some of the Important Kernel API

1: alloc_chrdev_region — register a range of char device numbers

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

Allocates a range of char device numbers. The major number will be chosen dynamically, and returned (along with the first minor number) in dev. Returns zero or a negative error code.

Device number is combination of MAJOR and MINOR number

there kernel macros to get major and minor number form device number

MAJOR(device_number) MINOR(device_number) or you can get your device number from major and minor number by MKDEV(major, minor)

2: cdev_init — initialize a cdev structure

void cdev_init (struct cdev * cdev, const struct file_operations * fops);

Initializes cdev, remembering fops, making it ready to add to the system with cdev_add.

3 : cdev_add — add a char device to the system

cdev_add adds the device represented by p to the system, making it live immediately. A negative error code is returned on failure.

4: class_create create a struct class structure

struct class * __class_create(struct module *owner, const char *name, struct lock_class_key *key)

5 : device_create - creates a device and registers it with sysfs

struct device * device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

This function can be used by char device classes. A struct device will be created in sysfs, registered to the specified class.

Important Structures

struct file_operations {

        struct module *owner;

        loff_t (*llseek) (struct file *, loff_t, int);

        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

        int (*open) (struct inode *, struct file *);

        int (*flush) (struct file *, fl_owner_t id);

        int (*release) (struct inode *, struct file *); 

... };

Charter Driver

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

/* Declarations */

static int skull_open(struct inode *inode, struct file *file);
static int skull_release(struct inode *inode, struct file *file);
ssize_t skull_read(struct file *flip, char __user *buf, size_t len, loff_t *off);
ssize_t skull_write(struct file *flip, const char __user *buf, size_t len, loff_t *off);


/* device number */
dev_t device_number;

/* class var */
static struct class *dev_class;

/* File Operation Structure */
static struct file_operations skull_fops = {
	.owner = THIS_MODULE,
	.read = skull_read,
	.write = skull_write,
	.open = skull_open,
	.release =  skull_release
};

/* cdev structure */
struct cdev skull_cdev;

/* kernel memory act as device */
#define MEM_SIZE 1024
uint8_t *kernel_buffer;


static int skull_open(struct inode *inode, struct file *file)
{
	if( (kernel_buffer = kmalloc(MEM_SIZE,GFP_KERNEL)) == 0){
		pr_info("kmalloc() failed can not allocate memory\n");
		return -1;
	}	
	pr_info("kmalloc() allocate memory device open\n");
	return 0;
}

static int skull_release(struct inode *inode, struct file *file)
{
	kfree(kernel_buffer);
	pr_info("kmalloc() dellocate memory device close\n");
	return 0;
}

ssize_t skull_read(struct file *flip, char __user *buf, size_t len, loff_t *off)
{
	copy_to_user(buf, kernel_buffer, MEM_SIZE);
	pr_info("copy_to_user() device read()\n");
	return MEM_SIZE;
}

ssize_t skull_write(struct file *flip,const char __user *buf, size_t len, loff_t *off)
{
	copy_from_user(kernel_buffer, buf, len);
	pr_info("copy_from_user() device write()\n");
	return len;
}

/* Driver init function */
static int __init char_dd_init(void){
	
	/* Get the dynamic Device number*/
	if(alloc_chrdev_region(&device_number,0,1,"skull")<0){
		pr_info("alloc_chrdev_region() failed\n");
		return -1;
	}

	/* Initialization of cdev */
	cdev_init(&skull_cdev,&skull_fops);

	/* add owner macro to owner field of cdev */
	skull_cdev.owner = THIS_MODULE;
	skull_cdev.ops = &skull_fops;

	/* Register the charecter device driver */
	if( cdev_add(&skull_cdev, device_number, 1) < 0)
	{
		pr_info("cdev_add() failed\n");
		goto r_class;
	}
	
	/* Register the class */
	if ( (dev_class = class_create(THIS_MODULE,"skull_class")) == NULL)
	{
		pr_info("class class_create() failed\n");
		goto r_class;
	}
	
	/* creating device */
	if ( (device_create(dev_class, NULL, device_number, NULL, "skull_device")) == NULL){
		pr_info("class class_create() failed\n");
		goto r_device;

	}
	
		

return 0;	// successfully initialized Device Driver

r_device:
	class_destroy(dev_class);

r_class:
	unregister_chrdev_region(device_number, 1);	
	return -1;
}


static void __exit char_dd_cleanup(void)
{
	/* 1  Device distory */
	device_destroy(dev_class, device_number);

	/* 2 class Distroy */
	class_destroy(dev_class);

	/* unregister cdev */
	cdev_del(&skull_cdev);
	/* Deallocation of chardriver region for free memory */
	unregister_chrdev_region(device_number, 1);

}

module_init(char_dd_init);
module_exit(char_dd_cleanup);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("pmvanker");
MODULE_DESCRIPTION("Charecter Device Driver dummy");
MODULE_INFO(board,"Beaglebone Black");


Test Application
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define DEVICE_FILE "/dev/skull_device"
#define MY_DATA "Hello Device File I am usr_app"

int main()
{
	int fd,ret=0;
	unsigned char usr_buff[100];
	memset(usr_buff,0,100);

	fd =  open(DEVICE_FILE, O_RDWR);
	if(fd<0)
	{
		perror("open");
	}	
	perror("open");

	ret = write(fd,MY_DATA,strlen(MY_DATA));
	if(ret<=0)
	{
		perror("write");
	}
	perror("write");
	
	ret =read(fd,usr_buff,strlen(MY_DATA));
	if(ret <=0)
	{
		perror("read");
	}
	perror("read");
	printf("data = %s\n",usr_buff);
	
	close(fd);
	perror("close");
	return 0;
}

$ make

$ gcc test.c

$ sudo ./a.out

Please visit my Youtube Video Tx