Wednesday, July 9, 2014

Character Device Drivers

  • Character drivers are easier to write as compared to block or character driver.
  • It is best suited for small devices.
  • As we know that in Linux everything is considered to be a file so devices are also considered to be a file.
  • These are special files called device files and are stored in /dev directory.
  • Lets first do ls -l (long listing) of the contents of /dev directory then we will see the intricacies of this directory.

 
  • We can identify the block driver with a special character "b" and character driver with special character "c".(you can observe the first character).
  • To understand bit more about file types please refer to the advanced topic section at the end of this page.
  • Lets observe the alarm device as shown above- This device is identified by two numbers  (10,58).
  • The first one is called the major number and the second one is called the minor number.
  • The major number is used by the kernel to identify which driver this device belongs to.
  • If there are multiple devices being served by the same driver then we have something called minor number.
  • Now I will show some of the character devices connected to my system
  •  dev_t variable(defined in linux/types.h) is used to store the device number.
  • 12 bit major number + 20 bit Minor number =32 bit dev_t
  • To obtain the major or minor parts of a dev_t we can use:
MAJOR(dev_t dev);
MINOR(dev_t dev);

  • If we know the major and minor numbers and want to turn it into a dev_t  we can use MKDEV(int major , int minor); 
  • To connect a device driver with its device file we need to do two things
1.Registering the driver with the device file
This can be done in two ways
a.If we know the device number in advance
int register_chardev_region(dev_t first,unsigned int count, char *name);
first here means the starting device number that we would want to allocate,
count means the number of contiguous device numbers that we would want to allocate(if the count is large then we might get non contiguous major numbers), name means the name of the device that should be associated with this number range.

it will appear in/proc/devices and sysfs
b. If we don't know the device number in advance
int alloc_chardev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);

dev- it is the output parameter for first assigned number
firstminor- first of the requested range of minor numbers
count- the number of minor numbers required
name-the name of the associated device or driver

This function 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.

2.Connecting the device file operations and device driver functions with each other


  • We must free the device number when we no longer need it, the ideal place to do it in the driver cleanup function.
void unregister_chardev_region(dev_t first,unsigned int count);

For the sake of completeness lets write a program to show the device number


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


static dev_t first; 

int __init devnum_init(void)
{
printk("entering init");
if (alloc_chrdev_region(&first, 0, 3, "pk") <0)
{
return -1;
}
printk(KERN_INFO "<Major number,Minor number>: <%d ,%d> \n",MAJOR(first),MINOR(first));
return 0;
}

static void __exit devnum_exit(void)/*Destructor*/
{
unregister_chrdev_region(first, 3);
printk("bye bye");
}
module_init(devnum_init);
module_exit(devnum_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("pk");
MODULE_DESCRIPTION("devicename module");




  • As we have already seen , we need to make a Makefile and then give a make command to build this module.
  • And then we will give insmod modulename.ko  and rmmod modulename.ko to insert and delete this module.
  • We can see the logs printed in kernel space by giving dmesg command.
 


  • Lets check if the device file has been created or not in /proc/devices directory

  •  we can see that the device file has been created .

  •  Now since we have reserved a device number to our driver so we can feel that we have connected the device and driver internally, but it is not so.
  • To establish full connection the driver must to the device file with file_operations structure.
  • This structure is defined in <linux/fs.h>.
  • This structure is a collection of function pointers and each open file is associated with its own set of functions.
  • These operations are mostly in charge of implementing system calls thats why they are named as open, close,read..e.t.c.


struct file_operations test_fops = {
.owner = THIS_MODULE,
.llseek = test_llseek,
.read = test_read,
.write = test_write,
.ioctl = test_ioctl,
.open = test_open,
.release = test_release,
}


To understand in details lets write a complete driver code and will see it in details.



/* Necessary includes for device drivers  refer to advanced topic for details */
#include <linux/init.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_from/to_user */

MODULE_LICENSE("Dual BSD/GPL");

/* Declaration of memory.c functions */
int memory_open(struct inode *inode, struct file *filp);
int memory_release(struct inode *inode, struct file *filp);
ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
ssize_t memory_write(struct file *filp, char *buf, size_t count, loff_t *f_pos);
void memory_exit(void);
int memory_init(void);

/* Structure that declares the usual file */
/* access functions */
struct file_operations memory_fops = {
  read: memory_read,
  write: memory_write,
  open: memory_open,
  release: memory_release
};

/* Declaration of the init and exit functions */
module_init(memory_init);
module_exit(memory_exit);

/* Global variables of the driver */
/* Major number */
int memory_major = 60;
/* Buffer to store data */
char *memory_buffer;


int memory_init(void) {
  int result;

  /* Registering device */
  result = register_chrdev(memory_major, "memory", &memory_fops);
  if (result < 0) {
    printk(
      "<1>memory: cannot obtain major number %d\n", memory_major);
    return result;
  }

  /* Allocating memory for the buffer */
  memory_buffer = kmalloc(1, GFP_KERNEL); 
  if (!memory_buffer) { 
    result = -ENOMEM;
    goto fail; 
  } 
  memset(memory_buffer, 0, 1);// initializing the buffer with 0

  printk("<1>Inserting memory module\n"); 
  return 0;

  fail: 
    memory_exit(); 
    return result;
}
void memory_exit(void) {
  /* Freeing the major number */
  unregister_chrdev(memory_major, "memory");

  /* Freeing buffer memory */
  if (memory_buffer) {
    kfree(memory_buffer);
  }

  printk("<1>Removing memory module\n");

}
int memory_open(struct inode *inode, struct file *filp) {

  /* Success */
  return 0;
}
int memory_release(struct inode *inode, struct file *filp) {
 
  /* Success */
  return 0;
}

ssize_t memory_read(struct file *filp, char *buf, 
                    size_t count, loff_t *f_pos) { 
 
  /* Transfering data to user space */ 
  copy_to_user(buf,memory_buffer,1);

  /* Changing reading position as best suits */ 
  if (*f_pos == 0) { 
    *f_pos+=1; 
    return 1; 
  } else { 
    return 0; 
  }
}

ssize_t memory_write( struct file *filp, char *buf,
                      size_t count, loff_t *f_pos) {

  char *tmp;

  tmp=buf+count-1;
  copy_from_user(memory_buffer,tmp,1);
  return 1;
}


Source: freesoftwaremagazine

In order for this driver to cummunicate with the userspace we create a device file by using-

# mknod /dev/memory c 60 0


You must have observed in the code that we have linked the driver with this device file by using register_chrdev function.While rehistering we have passed the mojor number, the driver(module name) and the file operations supported by the device file.

We have to compile the module and insert it in the same way we have been doing by giving make command and then insmod memory.ko command.

We must also give the device access by typing this command in terminal
chmod 666 /dev/memory (here our module name is memory)

To test this module's working lets give a command
echo-n abcd>/dev/memory 
Since it is a one byte driver so the last byte i.e d should be stored in the buffer(the bytes got overwritten)
We can verify this by giving the command cat /dev/memory



Advanced Topic:

 Identifying File types

SymbolMeaning
-Regular file
dDirectory
lLink
cSpecial file
sSocket
pNamed pipe
bBlock device

Header files used in Linux Device Drivers


#include <Linux/init.h> : Basic Initialization.

 #include <linux/module.h> :Writing any modules.

 #include <linux/version.h> : To ger linux version related data..

 #include <linux/kernel.h> :Allowed to use prinitk() function.

 #include <linux/fs.h> : To perform file related operations.

 #include <linux/device.h> : To perform device related operations.

 #include <linux/cdev.h> :To crated a character device driver with major and minor number.

#include <linux/slab.h> :Allow to use kmalloc() function.

 #include <linux/errno.h> :File contains error codes.

 #include <linux/types.h> :To get support to use data types like size_t ,dev_t .

 #include <linux/fcntl.h> :For o_accmode .

 #include <asm/system.h> :To use cli(), _flags

#include <asm/uaccess.h> :To use functions like copy_from /to _users.


Functions provided in module.h file

MODULE_ALIAS(_alias) :To provide
module name and 
information in 
user space.
MODULE_LICENSE(_license) Name of license
which modules 
will be using.
MODULE_AUTHOR() Writer of module.
MODULE_DESCRIPTION() Provide working
details of module.
MODULE_PARAM() Parameter which
pass while 
loading of 
module.
MODULE_VERSION() Version of
module under 
use.

To get all the above mentioned info in userspace give modinfo modulename.ko command in the terminal.


For any clarifications or if you have any suggestion please post comments below.

No comments:

Post a Comment