|
/***************************************************************************//** |
|
* \file driver.c |
|
* |
|
* \details Simple Linux device driver (Global Workqueue - Dynamic method) |
|
* |
|
* \author EmbeTronicX |
|
* |
|
*******************************************************************************/ |
|
#include <linux/kernel.h> |
|
#include <linux/init.h> |
|
#include <linux/module.h> |
|
#include <linux/kdev_t.h> |
|
#include <linux/fs.h> |
|
#include <linux/cdev.h> |
|
#include <linux/device.h> |
|
#include<linux/slab.h> //kmalloc() |
|
#include<linux/uaccess.h> //copy_to/from_user() |
|
#include<linux/sysfs.h> |
|
#include<linux/kobject.h> |
|
#include <linux/interrupt.h> |
|
#include <asm/io.h> |
|
#include <linux/workqueue.h> // Required for workqueues |
|
|
|
|
|
#define IRQ_NO 60 |
|
|
|
/* Work structure */ |
|
static struct work_struct workqueue; |
|
|
|
void workqueue_fn(struct work_struct *work); |
|
|
|
/*Workqueue Function*/ |
|
void workqueue_fn(struct work_struct *work) |
|
{ |
|
pr_info("Executing Workqueue Function\n"); |
|
} |
|
|
|
//Interrupt handler for IRQ_NO. |
|
static irqreturn_t irq_handler(int irq,void *dev_id) { |
|
pr_info("Shared IRQ: Interrupt Occurred"); |
|
/*Allocating work to queue*/ |
|
schedule_work(&workqueue); |
|
|
|
return IRQ_HANDLED; |
|
} |
|
|
|
volatile int etx_value = 0; |
|
dev_t dev = 0; |
|
static struct class *dev_class; |
|
static struct cdev etx_cdev; |
|
struct kobject *kobj_ref; |
|
|
|
/* |
|
** Function Prototypes |
|
*/ |
|
static int __init etx_driver_init(void); |
|
static void __exit etx_driver_exit(void); |
|
|
|
/*************** Driver functions **********************/ |
|
static int etx_open(struct inode *inode, struct file *file); |
|
static int etx_release(struct inode *inode, struct file *file); |
|
static ssize_t etx_read(struct file *filp, |
|
char __user *buf, size_t len,loff_t * off); |
|
static ssize_t etx_write(struct file *filp, |
|
const char *buf, size_t len, loff_t * off); |
|
|
|
/*************** Sysfs functions **********************/ |
|
static ssize_t sysfs_show(struct kobject *kobj, |
|
struct kobj_attribute *attr, char *buf); |
|
static ssize_t sysfs_store(struct kobject *kobj, |
|
struct kobj_attribute *attr,const char *buf, size_t count); |
|
|
|
//struct kobj_attribute etx_attr = __ATTR(etx_value, 0660, sysfs_show, sysfs_store); |
|
|
|
/* |
|
** File operation sturcture |
|
*/ |
|
static struct file_operations fops = |
|
{ |
|
.owner = THIS_MODULE, |
|
.read = etx_read, |
|
.write = etx_write, |
|
.open = etx_open, |
|
.release = etx_release, |
|
}; |
|
|
|
/* |
|
** This function will be called when we read the sysfs file |
|
*/ |
|
static ssize_t sysfs_show(struct kobject *kobj, |
|
struct kobj_attribute *attr, char *buf) |
|
{ |
|
pr_info("Sysfs - Read!!!\n"); |
|
return sprintf(buf, "%d", etx_value); |
|
} |
|
|
|
/* |
|
** This function will be called when we write the sysfsfs file |
|
*/ |
|
static ssize_t sysfs_store(struct kobject *kobj, |
|
struct kobj_attribute *attr,const char *buf, size_t count) |
|
{ |
|
pr_info("Sysfs - Write!!!\n"); |
|
sscanf(buf,"%d",&etx_value); |
|
return count; |
|
} |
|
|
|
/* |
|
** This function will be called when we open the Device file |
|
*/ |
|
static int etx_open(struct inode *inode, struct file *file) |
|
{ |
|
pr_info("Device File Opened...!!!\n"); |
|
return 0; |
|
} |
|
|
|
/* |
|
** This function will be called when we close the Device file |
|
*/ |
|
static int etx_release(struct inode *inode, struct file *file) |
|
{ |
|
pr_info("Device File Closed...!!!\n"); |
|
return 0; |
|
} |
|
/* |
|
** This function will be called when we read the Device file |
|
*/ |
|
static ssize_t etx_read(struct file *filp, |
|
char __user *buf, size_t len, loff_t *off) |
|
{ |
|
pr_info("Read function\n"); |
|
generic_handle_irq(IRQ_NO); |
|
return 0; |
|
} |
|
|
|
/* |
|
** This function will be called when we write the Device file |
|
*/ |
|
static ssize_t etx_write(struct file *filp, |
|
const char __user *buf, size_t len, loff_t *off) |
|
{ |
|
pr_info("Write Function\n"); |
|
return len; |
|
} |
|
|
|
/* |
|
** Module Init function |
|
*/ |
|
static int __init etx_driver_init(void) |
|
{ |
|
/*Allocating Major number*/ |
|
if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){ |
|
pr_info("Cannot allocate major number\n"); |
|
return -1; |
|
} |
|
pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev)); |
|
|
|
/*Creating cdev structure*/ |
|
cdev_init(&etx_cdev,&fops); |
|
|
|
/*Adding character device to the system*/ |
|
if((cdev_add(&etx_cdev,dev,1)) < 0){ |
|
pr_info("Cannot add the device to the system\n"); |
|
goto r_class; |
|
} |
|
|
|
/*Creating struct class*/ |
|
/*if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){ |
|
pr_info("Cannot create the struct class\n"); |
|
goto r_class; |
|
}*/ |
|
|
|
/*Creating device*/ |
|
/*if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){ |
|
pr_info("Cannot create the Device 1\n"); |
|
goto r_device; |
|
}*/ |
|
|
|
/*Creating a directory in /sys/kernel/ */ |
|
//kobj_ref = kobject_create_and_add("etx_sysfs",kernel_kobj); |
|
|
|
/*Creating sysfs file for etx_value*/ |
|
/*if(sysfs_create_file(kobj_ref,&etx_attr.attr)){ |
|
pr_err("Cannot create sysfs file......\n"); |
|
goto r_sysfs; |
|
}*/ |
|
if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "etx_device", (void *)(irq_handler))) { |
|
pr_info("my_device: cannot register IRQ "); |
|
goto irq; |
|
} |
|
|
|
/*Creating work by Dynamic Method */ |
|
INIT_WORK(&workqueue,workqueue_fn); |
|
|
|
pr_info("Device Driver Insert...Done!!!\n"); |
|
return 0; |
|
|
|
irq: |
|
free_irq(IRQ_NO,(void *)(irq_handler)); |
|
|
|
r_sysfs: |
|
//kobject_put(kobj_ref); |
|
//sysfs_remove_file(kernel_kobj, &etx_attr.attr); |
|
|
|
r_device: |
|
//class_destroy(dev_class); |
|
r_class: |
|
unregister_chrdev_region(dev,1); |
|
cdev_del(&etx_cdev); |
|
return -1; |
|
} |
|
|
|
/* |
|
** Module exit function |
|
*/ |
|
static void __exit etx_driver_exit(void) |
|
{ |
|
free_irq(IRQ_NO,(void *)(irq_handler)); |
|
//kobject_put(kobj_ref); |
|
//sysfs_remove_file(kernel_kobj, &etx_attr.attr); |
|
//device_destroy(dev_class,dev); |
|
//class_destroy(dev_class); |
|
cdev_del(&etx_cdev); |
|
unregister_chrdev_region(dev, 1); |
|
pr_info("Device Driver Remove...Done!!!\n"); |
|
} |
|
|
|
module_init(etx_driver_init); |
|
module_exit(etx_driver_exit); |
|
|
|
MODULE_LICENSE("GPL"); |
|
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>"); |
|
MODULE_DESCRIPTION("Simple Linux device driver (Global Workqueue - Dynamic method)"); |
|
MODULE_VERSION("1.11"); |