i.MX283开发板第一个Linux驱动-LED驱动改进

上一个博客i.MX283开发板第一个Linux驱动讲的是最简单的LED驱动的编写,但是其中还有一些不足。

首先是在利用insmod加载驱动时,需要用指令mknod /dev/imx283_led c 200 0手动创建设备节点,否则在/dev下是不会有我们的设备的,应用程序中open("/dev/imx283_led",O_RDWR)必然会失败。

其次是利用register_chrdev函数注册设备会造成设备号的浪费,这个是早期2.4版本内核的注册方法,在Linux2.6及以后的版本都不是很推荐这种方式。

下面就针对上一个LED驱动作下改进。

  • udev、mdev机制

Linux有udev、mdev的机制,而我们的ARM开发板上移植的busybox有mdev机制,然后mdev机制会通过class类来找到相应类的驱动设备来自动创建设备节点 (前提需要有mdev)

udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除,udev 可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。

1.首先要创建一个 cdev结构体变量、class 类和device设备。

static struct cdev LED_cdev;//定义一个cdev结构体
static struct class *led_class;//创建一个LED类
static struct device *led_device;//创建一个LED设备 该设备是需要挂在LED类下面的
static int major;//主设备号
static dev_t  dev_id;//设备号 由主设备号和次设备号组合而成

注意:dev_id是为了动态申请设备号创建的变量,申请成功后可以利用MAJOR(dev_id)和MINOR(dev_id)取出主设备号和次设备号。

2.改进注册与注销字符设备函数

register_chrdev()的弊端在于它仅仅由一个主设备号就确定了一个设备驱动,因为register_chrdev()的入口参数只有主设备号major和fops结构体,Linux内核最多支持255个字符设备,假设我有255个不同的字符设备需要控制,那么就需要255个主设备号,一下子用光了所有的设备号,这是很不合理的!

int __register_chrdev(unsigned int major, //主设备号
                      unsigned int baseminor,
                      unsigned int count,
                      const char *name,//设备名称
                      const struct file_operations *fops)

因此就需要一种合理的方式分配和释放设备号,Linux 2.6及以后的内核就提供了这种方式,下面这个函数就是用来申请设备号:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 
/*
dev:需要申请的设备号  
baseminor:次设备号起始位置
count:需要申请次设备号的个数 
name:设备名称
*/

这样就保证了每个设备只对应一个主设备号和一个次设备号 。同样的,有申请设备号必然有注销设备号,注销设备号函数如下:

void unregister_chrdev_region(dev_t from, unsigned count)
/*
from:需要注销设备号的起始位置
count:个数
*/

申请完设备号,就需要向内核注册字符设备,这里需要首先需要使用cdev_init()初始化一个cdev结构体。

struct cdev {
	struct kobject kobj;
	struct module *owner;//一般为THIS_MODULE
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;//设备号
	unsigned int count;//设备号数量
};

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
/*
cdev:初始化一个cdev结构体
fops:将设备驱动file_operations结构体赋给cdev的ops变量 
*/

 

接下来需要向刚刚初始化好的cdev结构体中添加字符设备:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
/*
p:初始化好的cdev结构体
dev:设备号
count:设备数量
*/

 

  • 改进后注册设备函数:

static int __init led_init(void)
{ 
   /*1.申请或者指定主设备号 此处由内核动态分配1个设备号*/
   alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME);
   /*2.初始化 LED_DEV结构体*/
   cdev_init(&LED_cdev, &led_fops);
   /*3.向LED_cdev添加1个设备*/
   cdev_add(&LED_cdev,dev_id, 1);
   /*4.创建一个LED类*/
   led_class = class_create(THIS_MODULE,"led_class");
   /*5.在LED类下面创建一个LED设备 然后mdev通过这个自动创建/dev/"DEVICE_NAME"*/
   led_device = device_create(led_class,NULL,dev_id,NULL,DEVICE_NAME);
   printk("module init ok\n");
   return 0; 
}

class_create是类创建函数,需要引用头文件#include

#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

/*
owner:THIS_MODULE
name:类名字,可以随意
*/

 

device_create用于在指定类下面创建一个设备

struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;

	va_start(vargs, fmt);
	dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
	va_end(vargs);
	return dev;
}
/*
class:指定类,这里指定上一步创建的类
parent:父设备,一般为NULL
devt:设备号,可以通过MKDEV构建出来
drvdata:是设备可能会使用的一些数据,一般为 NULL
fmt:设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。
MKDEV是个宏定义,作用是根据已有的主设备号和次设备号构建一个dev_t类型的设备号。
*/

 

  • 改进后注销设备函数:

static void __exit led_exit(void)
{
  /*1.删除LED_cdev结构体*/
  cdev_del(&LED_cdev); 
  /*2.注销设备 cat /proc/devices下不可见*/
   unregister_chrdev_region(dev_id,1);
  /*3.删除LED设备*/
  device_destroy(led_class, dev_id);
  /*4.删除LED类*/
  class_destroy(led_class);
  printk("module exit ok\n");
}

 device_destroy函数是从指定类下面删除一个设备

void device_destroy(struct class *class, dev_t devt)
{
	struct device *dev;

	dev = class_find_device(class, NULL, &devt, __match_devt);
	if (dev) {
		put_device(dev);
		device_unregister(dev);
	}
}
//class:指定类            devt:设备号

 

void class_destroy(struct class *cls)
{
	if ((cls == NULL) || (IS_ERR(cls)))
		return;

	class_unregister(cls);
}
//cls: 需要删除的class

 

下面给出完整的驱动函数:

/*
   GPIO Driver driver for EasyARM-iMX283
*/
#include //模块加载卸载函数
#include //内核头文件
#include //数据类型定义
#include 
#include 
#include //file_operations结构体
#include //class_create等函数
#include 
#include 
#include 
#include 
#include 
#include 
#include //gpio_request  gpio_free函数 

#include <../arch/arm/mach-mx28/mx28_pins.h>

#define DEVICE_NAME	"imx283_led"//驱动名称
#define LED_GPIO	MXS_PIN_TO_GPIO(PINID_LCD_D23)		//for 283 287A/B

static int led_open(struct inode *inode ,struct file *flip)
{

   int ret = -1;
   gpio_free(LED_GPIO); 
   ret = gpio_request(LED_GPIO, "LED1");
   printk("gpio_request = %d\r\n",ret);
   return 0;
}


static int led_release(struct inode *inode ,struct file *flip)
{
  gpio_free(LED_GPIO);
  return 0;  
}


static int led_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
   int ret = -1;
   unsigned char databuf[1];
   ret = copy_from_user(databuf,buf,1);
   if(ret < 0 ) 
   	{
   	  printk("kernel write error \n");
	  return ret;
   	}
   gpio_direction_output(LED_GPIO, databuf[0]);
   return ret; 
}


static  ssize_t led_read(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
    return 0;
}

static struct file_operations led_fops={
	.owner		= THIS_MODULE,
	.open 		= led_open,
	.write		= led_write,
	.read       = led_read,
	.release	= led_release,
};


static struct cdev LED_cdev;//定义一个cdev结构体
static struct class *led_class;//创建一个LED类
static struct device *led_device;//创建一个LED设备 该设备是需要挂在LED类下面的
static int major;//主设备号
static dev_t  dev_id; 

static int __init led_init(void)
{ 
    /*1.申请或者指定主设备号 此处由内核动态分配设备号*/
    alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME);
	/*2.初始化 LED_DEV结构体*/
   	cdev_init(&LED_cdev, &led_fops);
	/*3.向LED_cdev添加设备*/
   	cdev_add(&LED_cdev,dev_id, 1);
	
   //major = register_chrdev(0,DEVICE_NAME,&led_fops);  
   //if(major < 0)
   //{
   // printk("register_chrdev error %d\n",major); 
   // return major;
   //}
   //4.创建一个LED类
   led_class = class_create(THIS_MODULE,"led_class");
   //5.在LED类下面创建一个LED设备 然后mdev通过这个自动创建/dev/"DEVICE_NAME"
   led_device = device_create(led_class,NULL,dev_id,NULL,DEVICE_NAME);
   printk("module init ok\n");
   return 0; 
}


static void __exit led_exit(void)
{
  /*1.删除LED_cdev结构体*/
  cdev_del(&LED_cdev); 
  /*2.注销设备 cat /proc/devices下不可见*/
  //unregister_chrdev(major, DEVICE_NAME);
   unregister_chrdev_region(dev_id,1);
  /*3.删除LED设备*/
  device_destroy(led_class, dev_id);
  /*4.删除LED类*/
  class_destroy(led_class);
  printk("module exit ok\n");
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzx2020");

3.测试

将上述驱动文件,编译生成.ko文件在开发板上测试。

首先看下/dev下有么有LED设备节点

i.MX283开发板第一个Linux驱动-LED驱动改进_第1张图片

此时没有任何LED设备节点,然后再加载驱动。

i.MX283开发板第一个Linux驱动-LED驱动改进_第2张图片

驱动加载成功,我们再到/dev下看看

i.MX283开发板第一个Linux驱动-LED驱动改进_第3张图片

已经自动生成了设备设备节点,主设备号250,次设备号0. 

最后,我们再卸载掉这个驱动,并再次查看/dev和/proc/devices

i.MX283开发板第一个Linux驱动-LED驱动改进_第4张图片

都没有“imx283_led”这个设备存在,说明已经被删除掉了。

4.总结

1.实际上新的设备注册方法就是将以前的拆分罢了。

i.MX283开发板第一个Linux驱动-LED驱动改进_第5张图片

 register_chrdev()原型如下:

i.MX283开发板第一个Linux驱动-LED驱动改进_第6张图片

2.如果有多个同一类型的设备怎么注册?比如说有4个LED需要控制?

有两种方法,第一种当然是有几个设备就注册几个设备号,这样肯定没问题,但是如果设备数量很多,这种方法就不可取了。

第二种就是利用次设备号,这些设备共享一个主设备号,用次设备号区分彼此。

驱动程序中注册和注销函数就要作如下修改,这里以4个LED为例:

static int major;//主设备号
static struct device *led_device[4];//4个设备
static int __init led_init(void)
{ 
    unsigned char i; 
    /*1.申请或者指定主设备号 此处由内核动态分配设备号*/
    alloc_chrdev_region(&dev_id, 0, 4, DEVICE_NAME);//需要分配4个次设备号,0,1,2,3
	major = MAJOR(dev_id);//取出主设备号
	/*2.初始化 LED_DEV结构体*/
   	cdev_init(&LED_cdev, &led_fops);
	/*3.向LED_cdev添加设备*/
   	cdev_add(&LED_cdev,dev_id, 4);//添加4个设备
    //4.创建一个LED类
    led_class = class_create(THIS_MODULE,"led_class");
    //5.在LED类下面创建一个LED设备 然后mdev通过这个自动创建/dev/"DEVICE_NAME"
    for(i=0;i<4;i++)//创建4个设备
   	{
      led_device[i] = device_create(led_class,NULL,MKDEV(major, i),NULL,"imx283_led%d",i);
   	}
   printk("module init ok\n");
   return 0; 
}
static void __exit led_exit(void)
{
  unsigned char i; 
  /*1.删除LED_cdev结构体*/
  cdev_del(&LED_cdev); 
  /*2.注销设备 cat /proc/devices下不可见*/
   unregister_chrdev_region(dev_id,4);
  /*3.删除LED设备*/
  for(i=0;i<4;i++)
  {
       device_destroy(led_class, MKDEV(major, i));
  }
  /*4.删除LED类*/
  class_destroy(led_class);
  printk("module exit ok\n");
}

编译生成.ko文件,用insmod加载,查看cat /proc/devices

i.MX283开发板第一个Linux驱动-LED驱动改进_第7张图片

只生成了1个LED设备,原因是这4个LED共享一个主设备号,同时也共享一个file_operations结构体,它们只是名字和次设备号不同,再查看下/dev目录:

i.MX283开发板第一个Linux驱动-LED驱动改进_第8张图片

那么它们既然共享同一个file_operations结构体,那该如何单独控制每一个LED呢?

file_operations结构体的write函数、read函数和open函数的入口参数都可以取出LED的设备号

//write函数
static int led_write(struct file *filp, 
                     const char __user *buf, 
                     size_t count,
                     loff_t *f_pos)
//read函数
static  ssize_t led_read(struct file *filp, 
                         const char __user *buf, 
                         size_t count,
                         loff_t *f_pos)
int minor =MINOR(filp->f_path.dentry->d_inode->i_rdev);//取出次设备号

 

static int led_open(struct inode *inode ,struct file *flip)//open 函数
int minor = MINOR(inode->i_rdev);//取出次设备号
//int minor = iminor(inode);//取出次设备号

这样在应用程序中,调用open(/dev/led1)时,上面取出的minor=1,open(/dev/led3),上面的minor=3.write调用也是类似的情况,这样驱动程序只要根据不同的次设备号对相应的LED进行操作,就可以实现同一套驱动控制多个同类设备,且没有占用多个主设备号。

 

 

本文参考:

1.《嵌入式Linux应用完全开发手册》 

2.《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0》

3.分析Linux驱动函数register_chrdev_region

 

 

你可能感兴趣的:(#,EasyARM-imx283,Linux自动创建设备节点)