Linux驱动学习记录-新字符设备

        第一节介绍了字符驱动设备,开发重点是register_chrdev注册字符设备,unregister_chrdev注销字符设备。驱动加载需要mknod命令创建节点。这些都是老版本,现在学习新字符设备驱动开发。第一节提了一点点,申请设备号是用自动分配的方式就是新设备开发。

 

1.新字符设备注册

        Linux中cdev结构体表示一个字符设备,在include/linux/cdev.h中,定义如下

//字符结构体  重点是 dev和ops
struct cdev {
	struct kobject kobj;                //内嵌的内核对象
	struct module *owner;               //该字符设备所在的内核模块的对象指针.
	const struct file_operations *ops;  //操作函数结构体,描述所实现的方法,极为重要的结构体
	struct list_head list;              //用来将已经向内核注册的所有字符设备形成链表
	dev_t dev;                          //设备号结构体,主号和次号
	unsigned int count;                 //隶属于同一主设备号的次设备号的个数
};


//相关操作函数
void cdev_init(struct cdev *, const struct file_operations *);

struct cdev *cdev_alloc(void);

void cdev_put(struct cdev *p);

int cdev_add(struct cdev *, dev_t, unsigned);

void cdev_del(struct cdev *);

void cd_forget(struct inode *);

一个简单例子介绍其结构体和函数的用法:

struct cdev devtest; //定义字符设备结构体
dev_t dev;           //定义设备号结构体

struct file_operations opstest = {
	.owner = THIS_MODULE,
};                   //定义操作函数结构体

cdev_init (&devtest, &opstest);  //初始化字符设备,加入操作函数
cdev_add (&devtest, dev, 1);     //添加字符设备,dev是设备号,添加1个
cdev_del (&devtest);             //删除字符设备结构体

2.自动创建设备节点

        以前我们通过命令mknod手动创建设备节点,一个设备节点其实就是设备文件,Linux中成为设备文件。在Linux中,所有设备的访问都是文件访问。接下来介绍自动创建设备节点,在驱动中实现后,通过modprobe加载驱动后,会自动在/dev目录下创建对应的设备文件。

        Linux下通过udev来实现设备文件的创建和删除,使用busybox构建根文件系统的时候,busybox会创建一个udev的简化版本-mdev,嵌入式linux中我们使用mdev来实现设备节点的安装删除。

        首先要创建class类结构体,然后创建设备结构体。他们的定义都在include/linux/device.h中,在353行和723行,可自行查看其结构体内成员及其作用。

//480行,定义class结构体创建的别名
extern struct class * __must_check __class_create(struct module *owner,
						  const char *name,
						  struct lock_class_key *key);
extern void class_destroy(struct class *cls);

/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

//因此输入参数只有两个
struct class *class_create (struct module *owner, const char *name)
  • owner:一般为THIS_MODULE;
  • name:类的名字。
  • return:返回值是一个指向name(class结构体)的指针。

        创建好类以后,还不能实现自动创建设备节点,还需要在这个类下面创建设备(相当于mknod),使用device_create命令来创建。

struct device *device_create(struct class *class,
                            struct device *parent,
                            dev_t devt,
                            void *drvdata,
                            const char *fmt, ...)
  • class:这个设备创建在哪个类下面,class指针指向刚刚创建的“name”。
  • parent:这个设备的父设备,一般是NULL。
  • devt:设备号结构体,由主号和次号构成。
  • drvdata:设备可能会使用的一些数据,一般是NULL。
  • fmt:是设备名字,如果fmt=XXX,就会生成/dev/XXX这个设备
  • 。。。:还有其他参数略。
  • return:创建好的设备结构体指针。

参考例子如下:

struct class *led_class;    //led类的结构体
struct device *led_device;  //led设备结构体
dev_t devid;                //led设备号结构体

/*驱动入口函数*/
static int __init led_init(void)
{
    led_class = class_creat(THIS_MODULE, "led");                       //创建led类
    led_device = device_cerat(led_class, NULL, devid, NULL, "led");    //创建led设备
    return 0;
}
/*驱动出口函数*/
static void __exit led_exit(void)
{
    device_destroy(led_class, devid);   //注销led设备
    class_destroy(led_class);           //注销led类
}

module_init(led_init);
module_exit(led_exit);

3.设置设备文件私有数据

        列举一下前两小节涉及的变量类型:

  1. struct cdev          //cdev设备结构体
  2. dev_t                   //设备号结构体
  3. struct class          //类结构体
  4. struct device        //设备结构体

现在我们把这些变量设置为结构体属性,用来描述一个驱动设备。

/* 设备结构体 */
struct test_dev{
                dev_t devid;             /* 设备号 */
                struct cdev cdev;        /* cdev */
                struct class *class;     /* 类 */
                struct device *device;   /* 设备 */
                int major;               /* 主设备号 */
                int minor;               /* 次设备号 */
};

struct test_dev testdev;

/* open 函数 */
/* 注意这里的驱动open函数与测试app的open函数的区别! */
static int test_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &testdev; /* 设置私有数据 */
    return 0;
}

        这里做出补充:struct inode结构体用于描述设备文件的静态属性,包含但不限于dev_t 、cdev等变量描述。mknod每创建一个节点,都会对应一个唯一的inode结构体。struct file是文件结构体代表打开的文件,系统中每个打开的文件在内核空间中都会对应一个file结构体,在文件关闭后,内核会释放这个数据结构。详见:Linux--struct file结构体 - Sophie_h - 博客园。

        下图展示各个部分的调用关系:

4.程序编写

下面摘取正点原子的部分程序/03_newchrled。

#define NEWCHRLED_CNT			1		  	/* 设备号个数 */
#define NEWCHRLED_NAME			"newchrled"	/* 名字 */
#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 */
 
/* 寄存器物理地址 */

/* 映射后的寄存器虚拟地址指针 */

/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

struct newchrled_dev newchrled;	/* led设备 */

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &newchrled; /* 设置私有数据 */
	return 0;
}


/* 设备操作函数 */
static struct file_operations newchrled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
	u32 val = 0;

	/* 初始化LED */
	/* 1、寄存器地址映射 */

	/* 2、使能GPIO1时钟 */

	/* 3、设置GPIO1_IO03的复用功能,将其复用为   GPIO1_IO03,最后设置IO属性。*/
	 
	/* 4、设置GPIO1_IO03为输出功能 */

	/* 5、默认关闭LED */

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (newchrled.major) {		/*  定义了设备号 */
		newchrled.devid = MKDEV(newchrled.major, 0);
		register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);	/* 申请设备号 */
		newchrled.major = MAJOR(newchrled.devid);	/* 获取分配号的主设备号 */
		newchrled.minor = MINOR(newchrled.devid);	/* 获取分配号的次设备号 */
	}
	printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);	
	
	/* 2、初始化cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

	/* 4、创建类 */
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.class)) {
		return PTR_ERR(newchrled.class);
	}

	/* 5、创建设备 */
	newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.device)) {
		return PTR_ERR(newchrled.device);
	}
	
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/* 注销字符设备驱动 */
	cdev_del(&newchrled.cdev);/*  删除cdev */
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */

	device_destroy(newchrled.class, newchrled.devid);
	class_destroy(newchrled.class);
}

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

5.测试

与第三小节一样。

6.附录结构图

Linux驱动学习记录-新字符设备_第1张图片

 

你可能感兴趣的