天天看點

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");
           

繼續閱讀