天天看点

Linux驱动之在sys文件系统下创建节点的编程框架

在之前一篇博文讨论Linux内核空间与应用空间数据交流的几种方式提到了如下几种方式:

1.输入子系统,这个是单向的,只能内核->应用层。通常用于输入设备如按键、触摸屏将键值或者坐标上报给用户空间

2.文件操作集合,ioctl/read/write等函数,对应了字符设备等设备类型,这个是双向的,内核层和应用层可以互相发数据。通常用于各种需要对硬件设备进行读写的设备驱动程序

3.sys文件系统,也就是属性节点,同样也是双向的。通常用于读取或者修改驱动程序的配置,比如在一个由pwm控制的LED程序中设置一个属性节点,修改或者得到led灯的亮度值。

4.proc文件系统,这个我用的很少。比如可以通过cat /proc/kmsg查看内核打印消息,设置printk的输出等级等。

对于输入子系统使用的方法比较简单,也没什么好说的。文件操作合集(file_operations 结构体)是Linux设备驱动程序与用户空间进行数据交流的基础,/dev、/sys、/proc目录下的节点几乎都要实现文件操作合集后用户空间才能操作这些节点。本文主要介绍了在sys文件系统下创建节点的几种方式与驱动框架:

1.基于标准字符设备驱动框架在sys文件系统下创建节点并实现文件操作合集与用户空间进行数据交流,附上代码在文后。

2.基于平台设备驱动框架在sys文件系统下创建节点并实现文件操作合集与用户空间进行数据交流,附上代码在文后。

在之前讲sys文件系统时也有说到Linux的设备驱动模型主要包括三大组件,分别是总线、设备和驱动。总线将设备与驱动绑定。一个现实的Linux 设备与驱动通常都需要挂接在一种总线比如PCI、USB、I2 C、SPI等。但是在嵌入式系统里面,SOC系统中集成的独立的外设控制器、挂接在SOC内存空间的外设等却不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称之platform总线,相应的设备称为platform_device,而驱动程序称为platform_driver。在内核源码里比较常见的gpio按键驱动程序里就采用了平台设备驱动框架。

程序一、基于标准字符设备驱动框架在sys文件系统下创建节点并实现文件操作合集与用户空间进行数据交流。

#include <linux/kernel.h>
#include <linux/module.h>  //驱动模块
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/slab.h>  /*动态内存分配*/
#include <linux/cdev.h>

#define CHAR_OFF 0
#define CHAR_OFF 1

struct char_dev {
	int value;
	dev_t char_dev_t;
	struct class *chardev_class;
	int char_value;
	struct device *dev;
};

static struct device *device = NULL;

static ssize_t cdev_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct char_dev *c_dev = (struct char_dev *)dev_get_drvdata(device);
	return sprintf(buf, "%d\n", c_dev->char_value);
}
		
static ssize_t cdev_store(struct device *dev, struct device_attribute *attr, char *buf, size_t count)
{
	int  value = 0;	
	struct char_dev *c_dev = (struct char_dev *)dev_get_drvdata(device);	
	kstrtoull(buf, 10, &value);	
	c_dev->char_value = value;	
	return count;
}

static DEVICE_ATTR(chardevice, S_IRUGO | S_IWUSR, cdev_store, cdev_show);

static int cdev_open(struct inode *inode, struct file *file)
{
	printk("cdev open succussfull\n");
	return 0;
}

static int cdev_release(struct inode *inode, struct file *file)
{
	struct char_dev *c_dev = (struct char_dev *)dev_get_drvdata(device);	
	device_remove_file(c_dev->dev, &dev_attr_vibration);
	device_destroy(c_dev->chardev_class,c_dev->motor_dev_t);
	class_destroy(c_dev->chardev_class);
	dev_set_drvdata(c_dev, NULL);
	return 0;
}

static long cdev_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{	
	struct char_dev *c_dev = (struct char_dev *)dev_get_drvdata(device);
	switch(cmd){
		case CHAR_ON:
			c_dev->value = 1;
			break;
		case CHAR_OFF:
			c_dev->value = 0;
			break;
		default:
			printk("cmd error\n");
			break;			
	}
	return 0;
}

ssize_t cdev_read(struct file *file, char __user *buff, size_t count, loff_t *loff )//设置低电平马达不转
{
	struct char_dev *c_dev = (struct char_dev *)dev_get_drvdata(device);
	c_dev->value = 0;
	return count;
}

ssize_t cdev_write(struct file *file, const char __user *buff, size_t count, loff_t *loff)//设置高电平马达转动
{
	struct char_dev *c_dev = (struct char_dev *)dev_get_drvdata(device);
	c_dev->value = 1;
	return count;
}

static struct file_operations chrdev_fops=
{
	.open		=	cdev_open,
	.release	=	cdev_release,
	.read		=	cdev_read,
	.write		=	cdev_write,
	.unlocked_ioctl		=	cdev_ioctl
};

static int __init cdev_init(void)//模块加载时的初始化工作
{
	int ret = 0;
	struct cdev *cdev = NULL;//cdev核心结构体指针
	struct char_dev *c_dev = kzalloc(sizeof(struct char_dev), GFP_KERNEL);
	if (!c_dev)
		return -ENOMEM;
	
	c_dev->vibration_level = 1;
	c_dev->value = 0;

	alloc_chrdev_region(&c_dev->motor_dev_t,0,1,"cdev_motor");//动态分配设备号

	cdev = cdev_alloc();//动态分配cdev结构体	
	
	memset(cdev,0,sizeof(struct cdev));		

	cdev_init(cdev, &chrdev_fops);	//初始化cdev结构体  

	ret = cdev_add(cdev,c_dev->motor_dev_t,1);		//注册cdev结构体

	if(ret) {
		return -EINVAL;
	}
		
	c_dev->chardev_class = class_create(THIS_MODULE,"chardev");	//创建类   

	c_dev->dev = device_create(c_dev->chardev_class, NULL, c_dev->char_dev_t, NULL, "cdev%d", 0);//创建设备节点	

	device = c_dev->dev;

	dev_set_drvdata(c_dev->dev, c_dev);			//设置设备驱动数据
	
	ret = device_create_file(c_dev->dev, &dev_attr_chardevice);
	
	return 0;	
}
 
static void __exit cdev_exit(void)
{
	/*释放设备节点*/
	printk("cdev_exit\n");	
	struct char_dev *c_dev = (struct char_dev *)dev_get_drvdata(device);
	device_remove_file(c_dev->dev, &dev_attr_chardevice);
	
	device_destroy(c_dev->chardev_class, c_dev->motor_dev_t);
	
	/*释放类指针*/
	class_destroy(c_dev->chardev_class);
	dev_set_drvdata(c_dev->dev, NULL);
}
 
module_init(cdev_init);		//指定模块的初始化函数的宏
module_exit(cdev_exit);		//指定模块的卸载函数的宏		
MODULE_LICENSE("GPL");		// 协议声明(许可证)
MODULE_AUTHOR("lucky");		//驱动编写的作者声明
           

程序二、基于平台设备驱动框架在sys文件系统下创建节点并实现文件操作合集与用户空间进行数据交流。

1.设备端程序基本框架

#include <linux/kernel.h>
#include <linux/module.h>  //驱动模块
#include <linux/platform_device.h>

//平台设备释放函数
static  void platdev_release(struct device *dev)
{

}

/*资源结构体*/
static struct resource	dev_resource[]=
{
	// [0]=
	// {
    //     .start=0x114000A0,
	// 	.end=0x114000A4,
	// 	.name="led",
	// 	.flags=IORESOURCE_MEM,
	// },
};

static struct platform_device pdev=
{
	.name="platform_device",    /*设备名称*/
	.id=0,   /*设备ID*/
	.dev=
	{
        .release=platdev_release,  /*释放函数*/
	},
	.num_resources=1,   /*资源数量*/
	.resource=dev_resource         /*资源结构体指针*/
};

static int __init plat_init(void)
{	
  /*1. 注册平台设备*/
  if(platform_device_register(&pdev))
  {
	 printk("设备注册失败!\n");
	 return -1; 
  }
  
    printk("设备注册成功!\n");
	return 0;
}

static void __exit plat_exit(void)
{
	/*2. 注销平台设备*/
	platform_device_unregister(&pdev);
	
	printk("设备卸载成功!\n");
}

module_init(plat_init);
module_exit(plat_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lucky");
           

2.驱动端程序基本框架

#include <linux/kernel.h>
#include <linux/module.h>  //驱动模块
#include <linux/platform_device.h>
#include <linux/device.h>

struct plat_dev {
	int value;
	int plat_value;
};

static ssize_t pdev_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct plat_dev *p_dev_data = (struct plat_dev *)dev_get_drvdata(device);
	return sprintf(buf, "%d\n", c_dev->plat_value);
}
		
static ssize_t pdev_store(struct device *dev, struct device_attribute *attr, char *buf, size_t count)
{
	int  value = 0;	
	struct plat_dev *p_dev_data = (struct plat_dev *)dev_get_drvdata(device);	
	kstrtoull(buf, 10, &value);	
	p_dev_data->plat_value = value;	
	return count;
}

static DEVICE_ATTR(platdevice, S_IWUSR | S_IRUGO, pdev_show, pdev_store);

static struct attribute *platdev_attrs[] = {
	&dev_attr_platdevice.attr,
	NULL,
};

static struct attribute_group platdev_attr_group = {
	.attrs = gpio_keys_attrs,
};

static int drv_probe(struct platform_device *pdev)
{
    int error = 0;
    struct device *dev = &pdev->dev;
    struct plat_dev *p_dev_data = dev_get_platdata(dev);
    if (!p_dev)
        return -ENOMEM;

    dev_set_drvdata(pdev->dev, p_dev_data);
    error = sysfs_create_group(&pdev->dev.kobj, &platdev_attr_group);
    if(error) {
        printk("不能创建sys节点\n");
        return error;
    }

    return 0;
}

static int drv_remove(struct platform_device *pdev)
{
	printk("资源卸载成功!\n");
    return 0;
}

static struct platform_driver drv=
{
    .probe=drv_probe,
	.remove= drv_remove,
	.driver=
	{
      .name="platform_device",
	},
};

static int __init plat_init(void)
{	
    /*1. 平台驱动端的注册*/
    if(platform_driver_register(&drv)) {
        printk("驱动端注册失败!\n");
        return -1;
    }
    printk("平台驱动安装成功!\n");
    //如果想要实现文件操作合集,可仿照标准字符设备驱动框架创建/dev下的设备节点
    /*
    alloc_chrdev_region(....);
	]cdev_alloc();		
	cdev_init(....); 	
	cdev_add(...); 
	class_create(...);    //创建类
	device_create(....);//注册设备
    */
    return 0;
}

static void __exit plat_exit(void)
{
	platform_driver_unregister(&drv);
	printk("hello 驱动卸载成功!\n");
}
module_init(plat_init);
module_exit(plat_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lucky");