字符驱动demo
测试内核版本为linux-4.4
proc 接口有些变化,使用了seq_file.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/cdev.h>
#include <linux/semaphore.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/slab.h>
#define FREG_DEVICE_NAME "freg"
#define FREG_DEV_NAME "freg"
struct freg_dev{
struct cdev dev;
struct semaphore sem;
int val;
};
static struct freg_dev * _freg_dev;
static struct class * _freg_class;
static void freg_create_proc(void);
static int freg_open(struct inode * inode,struct file * flip)
{
struct freg_dev * dev = NULL;
dev = container_of(inode->i_cdev,struct freg_dev,dev);
flip->private_data = dev;
pr_info("1. freg_open .\n");
return 0;
}
static int freg_close(struct inode*inode,struct file * flip)
{
return 0;
}
static ssize_t freg_read(struct file * filp,char __user * buffer,size_t count,loff_t * off)
{
int err = 0;
struct freg_dev * dev = (struct freg_dev*)filp->private_data;
if(down_interruptible(&(dev->sem))){
return -ERESTARTSYS;
}
if(copy_to_user(buffer,&(dev->val),sizeof(dev->val))){
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
static ssize_t freg_write(struct file * filp,const char __user * buffer,size_t count,loff_t * off)
{
int err = 0;
struct freg_dev * dev = (struct freg_dev *)filp->private_data;
if(down_interruptible(&(dev->sem))){
return -ERESTARTSYS;
}
if(count < sizeof(dev->val)){
pr_err("freg user buffer is to small !!! \n");
err = -EFAULT;
goto out;
}
if(copy_from_user(&(dev->val),buffer,count)){
pr_err("freg copy from user failed,count %ld \n",count);
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
static struct file_operations freg_fops = {
.owner = THIS_MODULE,
.open = freg_open,
.release = freg_close,
.read = freg_read,
.write = freg_write,
};
static ssize_t _freg_set_val(struct freg_dev * dev,const char * buf,size_t count)
{
int val = 0;
val = simple_strtol(buf,NULL,10);
if(down_interruptible(&(dev->sem))){
return -ERESTARTSYS;
}
dev->val = val;
up(&(dev->sem));
return count;
}
static ssize_t _freg_get_val(struct freg_dev * dev,char * buf)
{
int val = 0;
if(down_interruptible(&(dev->sem))){
return -ERESTARTSYS;
}
val = dev->val;
up(&(dev->sem));
return snprintf(buf,PAGE_SIZE,"%d\n",val);
}
static int seq_show(struct seq_file * file,void * data)
{
struct freg_dev * dev = file->private;
seq_printf(file,"freg value : %d \n",dev->val);
return 0;
}
static int __freg_proc_open(struct inode * inode,struct file * filp)
{
return single_open(filp,seq_show,_freg_dev);
}
static ssize_t __freg_proc_write(struct file * filp,const char __user * buffer,size_t count,loff_t * off)
{
int val = 0;
char * page = NULL;
struct seq_file * file = (struct seq_file *)filp->private_data;
struct freg_dev * dev = (struct freg_dev *)file->private;
if(!dev)
{
pr_err("freg proc failed ,Not init freg_dev\n");
return -EFAULT;
}
if(count > PAGE_SIZE){
pr_err("freg proc write data size is to large !!! \n");
return -EFAULT;
}
if(seq_write(file,buffer,count) < 0)
{
pr_err("freg seq_write failed \n");
return -EFAULT;
}
page =(char *) __get_free_page(GFP_KERNEL);
if(!page){
pr_err("freg failed to alloc page \n");
return -ENOMEM;
}
if(copy_from_user(page,buffer,count)) {
pr_err("freg proc copy from user failed \n");
free_page((unsigned long)page);
return -EFAULT;
}
val = _freg_set_val(dev,page,count);
return 0;
}
static const struct file_operations proc_fops = {
.owner = THIS_MODULE,
.open = __freg_proc_open,
.release = seq_release,
.read = seq_read,
.write = __freg_proc_write,
.llseek = seq_lseek,
};
static void freg_create_proc(void)
{
struct proc_dir_entry * entry = NULL;
entry = proc_create(FREG_DEV_NAME,0,NULL,&proc_fops);
if(!entry){
pr_err("proc create failed \n");
}
}
static void freg_remove_proc(void)
{
remove_proc_entry(FREG_DEV_NAME,NULL);
}
static ssize_t freg_val_show(struct device * device,struct device_attribute * attr,char * buf)
{
struct freg_dev * dev = (struct freg_dev *)dev_get_drvdata(device);
return _freg_get_val(dev,buf);
}
static ssize_t freg_val_store(struct device * device,struct device_attribute * attr,const char *buf,size_t count)
{
struct freg_dev * dev = (struct freg_dev *)dev_get_drvdata(device);
return _freg_set_val(dev,buf,count);
}
static DEVICE_ATTR(val,S_IRUGO | S_IWUSR,freg_val_show,freg_val_store);
static int __init freg_init(void)
{
int err = -1;
dev_t dev;
struct device * device = NULL;
pr_info("freg_init ... \n");
err = alloc_chrdev_region(&dev,0,1,FREG_DEV_NAME);
if(err < 0){
pr_err("freg_dev alloc_chrdev_region failed \n");
goto fail;
}
_freg_dev = kmalloc(sizeof(struct freg_dev),GFP_KERNEL);
if(!_freg_dev){
pr_err("freg_dev kmalloc failed ..");
err = -ENOMEM;
goto unregister;
}
memset(_freg_dev,0,sizeof(struct freg_dev));
cdev_init(&(_freg_dev->dev),&freg_fops);
_freg_dev->dev.owner = THIS_MODULE;
_freg_dev->dev.ops = &freg_fops;
err = cdev_add(&(_freg_dev->dev),dev,1);
if(err < 0){
pr_err("cdev add failed \n");
goto register_cdev_fail;
}
sema_init(&(_freg_dev->sem),0);
_freg_dev->val = 0;
_freg_class = class_create(THIS_MODULE,FREG_DEV_NAME);
if(IS_ERR(_freg_class)){
err = PTR_ERR(_freg_class);
pr_err("freg class create failed\n");
goto register_class;
}
device = device_create(_freg_class,NULL,dev,NULL,"%s",FREG_DEV_NAME);
if(IS_ERR(device)){
err = PTR_ERR(device);
pr_err("freg device create failed \n");
goto register_dev;
}
err = device_create_file(device,&dev_attr_val);
if(err < 0){
pr_err("freg device create file failed \n");
goto register_attr;
}
dev_set_drvdata(device,_freg_dev);
freg_create_proc();
return 0;
register_attr:
device_destroy(_freg_class,dev);
register_dev:
class_destroy(_freg_class);
register_class:
cdev_del(&(_freg_dev->dev));
register_cdev_fail:
kfree(_freg_dev);
unregister:
unregister_chrdev_region(dev,1);
fail:
return err;
}
static void __exit freg_exit(void)
{
freg_remove_proc();
cdev_del(&(_freg_dev->dev));
unregister_chrdev_region(_freg_dev->dev.dev,1);
if(_freg_class)
class_destroy(_freg_class);
if(_freg_dev)
kfree(_freg_dev);
}
module_init(freg_init);
module_exit(freg_exit);