Search code examples
clinux-kernellinux-device-driverdevice-driver

Segmentation Fault in Linux Device Driver


The following code throws a segmentation fault when I try to read from the device (eg: cat /dev/device_name) . However, If i remove the calls to down_interruptible and up from the klg_read and klg_write methods, and move them to klg_open and klg_close respectively, the code works fine. I am experimenting with device drivers and would like to understand why this segmentation fault occurs. Thank you!

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/interrupt.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include<asm/io.h>
#include<linux/semaphore.h>
#include<linux/spinlock.h>
#include<linux/sched.h>
#include<linux/device.h>



#define DEVICE_NAME "klg_app"
#define AUTHOR "morpheus15"
#define LICENSE "GPL"
#define CLASS_NAME "klg_class"
#define FOR(i,x,n) for(i=x;i<n;i++)
#define BUF_MAX_SIZE 10
#define FIRST_MINOR_NUM 0
#define MINOR_DEVICE_COUNT 1

typedef struct
{
  char data[BUF_MAX_SIZE];
}klg_device_t;


static klg_device_t klgdev;
static int ret,major;
static dev_t dev_num;
static struct cdev *kcdev = NULL;
static struct class *klg_class = NULL;
static struct semaphore semm;

static int klg_open(struct inode *klg_inode,struct file *filp)
{
  filp->private_data = &klgdev;
  /*if(down_interruptible(&semm))
    {
      printk(KERN_INFO "could not open device %s\n",DEVICE_NAME);
      return -ERESTARTSYS;
    }
    */
  printk(KERN_INFO "device %s called open()\n",DEVICE_NAME);
  return 0;
}

static int klg_close(struct inode *klg_inode,struct file *filp)
{
  //up(&semm);
  printk(KERN_INFO "device %s called close()\n",DEVICE_NAME);
  return 0;
}

static ssize_t klg_read(struct file *filp,char __user *buf,size_t count,loff_t *pos)
{
  printk(KERN_INFO "device %s called read()\n",DEVICE_NAME);
  klg_device_t *klgp = filp->private_data;
   if (down_interruptible(&semm))
    {
      printk(KERN_INFO "device %s already in use. could not open for reading\n",DEVICE_NAME);
      return -ERESTARTSYS;
    }

  int len = strlen(klgp->data);
  if (!len || (*pos) > len)
    {
       up(&semm);
      return 0;
    }


  if (*pos + count > len)
    {
      count = len - *pos;
    }

  ret = copy_to_user(buf,klgp->data,count);
  if (ret)
    {
      printk(KERN_INFO "copy_to_user() failed for device %s, exit with error %d\n",DEVICE_NAME,ret);
      up(&semm);
      return -EFAULT;
    }

  (*pos) += count;
   up(&semm);
  return count;

}

static ssize_t klg_write(struct file *filp,char __user *buf,size_t count,loff_t *pos)
{


  printk(KERN_INFO "device %s called write()\n",DEVICE_NAME);
  klg_device_t *klgp = filp->private_data;
   if (down_interruptible(&semm))
    {
      printk(KERN_INFO "device %s already in use. Could not open for writing\n",DEVICE_NAME);
      return -ERESTARTSYS;
    }


  memset(klgp->data,0,BUF_MAX_SIZE);

  if (count  > BUF_MAX_SIZE)
    {
      count = BUF_MAX_SIZE;
    }

  ret = copy_from_user(klgp->data,buf,count);
  if (ret)
    {
      printk(KERN_INFO "copy_from_user() failed for device %s, exit with error %d\n",DEVICE_NAME,ret);
        up(&semm);
      return -EFAULT;
    }

  up(&semm);
  return count;


}


static struct file_operations fops = 
  {
    .owner = AUTHOR,
    .open = klg_open,
    .release = klg_close,
    .read = klg_read,
    .write = klg_write
  };



 static void initialize_constructs(void)
{
  sema_init(&semm,1);
}

static int __init klg_init(void)
{
  ret = alloc_chrdev_region(&dev_num,FIRST_MINOR_NUM,MINOR_DEVICE_COUNT,DEVICE_NAME);
  if (ret)
    {
      printk(KERN_INFO "could not alloc_chrdev_region for device %s\n",DEVICE_NAME);
      return -1;
    }
  major = MAJOR(dev_num);
  printk(KERN_INFO "registered device %s with major number %d\n",DEVICE_NAME,major);


  // add the device to /sys/class/CLASS_NAME
  klg_class = class_create(DEVICE_NAME,CLASS_NAME);
  if (!klg_class)
    {
      printk(KERN_INFO "could not add device %s to /sys/class/%s\n",CLASS_NAME);
      unregister_chrdev_region(dev_num,MINOR_DEVICE_COUNT);
      return -2;
    }

  // create an entry in /dev for the device file
  if (!device_create(klg_class,NULL,dev_num,NULL,DEVICE_NAME))
    {
      printk(KERN_INFO "could not create entry /dev/%s for device %s\n",DEVICE_NAME,DEVICE_NAME);
      class_destroy(klg_class);
      unregister_chrdev_region(dev_num,MINOR_DEVICE_COUNT);
      return -2;
    }

  // create the cdev struct for the character device
  kcdev = cdev_alloc();
  kcdev->owner = AUTHOR;
  kcdev->ops = &fops;
  ret = cdev_add(kcdev,dev_num,MINOR_DEVICE_COUNT);
  if (ret)
    {
      printk(KERN_INFO "could not allocate a char dev struct for device %s\n",DEVICE_NAME);
      device_destroy(klg_class,dev_num);
      class_destroy(klg_class);
      unregister_chrdev_region(dev_num,MINOR_DEVICE_COUNT);
      return -1;
    }


  initialize_constructs();
  printk(KERN_INFO "successfully created a character device driver for device %s\n",DEVICE_NAME);
  return 0;
}

static void __exit klg_exit(void)
{
  // destroy in the reverse order
  cdev_del(kcdev);
  device_destroy(klg_class,dev_num);
  class_destroy(klg_class);
  unregister_chrdev_region(dev_num,MINOR_DEVICE_COUNT);
  printk(KERN_INFO "successfully unregistered character device %s and all its components\n",DEVICE_NAME);
}


module_init(klg_init);
module_exit(klg_exit);
MODULE_AUTHOR(AUTHOR);
MODULE_LICENSE(LICENSE);
MODULE_DESCRIPTION("this is a sample application");

Solution

  • As @Tsyvarev observed simply by looking at your code, you're not properly initializing the owner field of your driver and file operations:

    warning: assignment from incompatible pointer type [enabled by default]
    kcdev->owner = AUTHOR;
    
    warning: initialization from incompatible pointer type [enabled by default]
     .owner = AUTHOR,
    

    Because of that every time you issue the open syscall on your driver you'll get something like this:

    BUG: unable to handle kernel paging request at 75ae1601
    IP: [<x109b89f>] try_module_get+0x1f/0x80
    *pde = 00000000
    Oops: 0000 [#1] SMP
    ...
    

    Just change it to:

    kcdev->owner = THIS_MODULE;
    

    and:

    static struct file_operations fops = 
    {
        .owner = THIS_MODULE;
        ...
    };
    

    There are also some other warnings in your code. You might want to take a look at those as well.