Linux驱动.之字符设备驱动框架,及手动,自动注册创建/dev下设备节点详解(一)

参考原文链接:https://blog.youkuaiyun.com/weixin_42963900/article/details/133897956
参考了其他人的,正点原子

本篇主要讲,字符设备的初级阶段,不带dts设备树的,对初学者,非常友好。

在这里插入图片描述
在这里插入图片描述

一、Linux 字符设备驱动框架
Linux一切皆文件,通过VFS虚拟文件系统,把所有外设的细节都隐藏起来,最后都以文件的形态呈现于应用层,
操作设备,就像对文件的打开、关闭、读写、控制。这种方式完美的统一了对用户的接口,极大方便了应用层的调用方式。

应用程序与VFS之间的接口是系统调用。VFS向下与具体的文件系统或设备文件之间的接口是file_operations结构体成员函数。file_operations是一个内核的结构体,其成员函数为真正实现对硬件的打开、关闭、读写、控制的一系列成员函数。

linux的文件种类:
-:普通文件
d:目录文件
p:管道文件
s:本地socket文件
l:链接文件
c:字符设备
b:块设备

在这里插入图片描述

设备分类
Linux内核按驱动程序实现模型框架的不同,将设备分为三类:

1、字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存
2、块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节,随机访问,设缓存以提高效率
3、网络设备:针对网络数据收发的设备

在这里插入图片描述

当应用层面要使用某个字符设备时(比如LED灯),只需要通过统一的系统函数open()、read()、write()、close()等函数来操作与该设备所对应的字符设备文件(比如/dev/led)即可。
驱动就是完成,xxx_open()、xxx_read()、xxx_write)、xxx_close()等函数,内核真正的控制硬件寄存器数据的读写,时序控制,来具体的操作硬件设备。因此驱动程序的开发本质就是完成xxx_open()等内核驱动程序。

在这里插入图片描述

二、Linux2.6版本老内核的注册字符设备的方法

1、注册字符设备的内核接口

在注册字符设备的时候,可使用register_chrdev()函数,其对应的注销函数是unregister_chrdev(),其定义是在include\linux\fs.h文件

首先分析register_chrdev()函数

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}

该函数会调用__register_chrdev()函数,其定义位于fs\char_dev.c文件

int __register_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name,const struct file_operations *fops)
{
	struct char_device_struct *cd;
	struct cdev *cdev;
	int err = -ENOMEM;

	cd = __register_chrdev_region(major, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);

	cdev = cdev_alloc();
	if (!cdev)
		goto out2;

	cdev->owner = fops->owner;
	cdev->ops = fops;
	kobject_set_name(&cdev->kobj, "%s", name);

	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
	if (err)
		goto out;

	cd->cdev = cdev;

	return major ? 0 : cd->major;
out:
	kobject_put(&cdev->kobj);
out2:
	kfree(__unregister_chrdev_region(cd->major, baseminor, count));
	return err;
}

__register_chrdev()函数首先调用__register_chrdev_region()函数

static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)
{
	struct char_device_struct *cd, **cp;
	int ret = 0;
	int i;

	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);

	mutex_lock(&chrdevs_lock);

	if (major == 0) {
		ret = find_dynamic_major();
		if (ret < 0) {
			pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
			       name);
			goto out;
		}
		major = ret;
	}

	if (major >= CHRDEV_MAJOR_MAX) {
		pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n",
		       name, major, CHRDEV_MAJOR_MAX-1);
		ret = -EINVAL;
		goto out;
	}

	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));

	i = major_to_index(major);

	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
		if ((*cp)->major > major ||
		    ((*cp)->major == major &&
		     (((*cp)->baseminor >= baseminor) ||
		      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
			break;

	/* Check for overlapping minor ranges.  */
	if (*cp && (*cp)->major == major) {
		int old_min = (*cp)->baseminor;
		int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
		int new_min = baseminor;
		int new_max = baseminor + minorct - 1;

		/* New driver overlaps from the left.  */
		if (new_max >= old_min && new_max <= old_max) {
			ret = -EBUSY;
			goto out;
		}

		/* New driver overlaps from the right.  */
		if (new_min <= old_max && new_min >= old_min) {
			ret = -EBUSY;
			goto out;
		}

		if (new_min < old_min && new_max > old_max) {
			ret = -EBUSY;
			goto out;
		}

	}

	cd->next = *cp;
	*cp = cd;
	mutex_unlock(&chrdevs_lock);
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}

其次调用cdev_alloc()函数

struct cdev *cdev_alloc(void)
{
	struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
	if (p) {
		INIT_LIST_HEAD(&p->list);
		kobject_init(&p->kobj, &ktype_cdev_dynamic);
	}
	return p;
}

然后调用cdev_add()函数

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;

	p->dev = dev;
	p->count = count;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}

综上register_chrdev函数调用流程如下

register_chrdev()
	-->__register_chrdev()
		-->__register_chrdev_region()
		-->cdev_alloc()
		-->cdev_add()

从__register_chrdev_region()函数可以看出,该函数会根据传入的参数,去完善结构体 char_device_struct(除cdev结构体),并返回该结构体地址

static struct char_device_struct {
	struct char_device_struct *next;
	unsigned int major;
	unsigned int baseminor;
	int minorct;
	char name[64];
	struct cdev *cdev;		/* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

该结构体中,存在一个链表,链表的作用分析如下:
#define CHRDEV_MAJOR_HASH_SIZE 255
1
可以看出分配的结构体指针数组有255个元素,由于有的主设备号大于254,故需要除以255取余,这样,当主设备号为256时,除以255取余为1,故存在数组chrdev[1]中,但chrdev也可存主设备号为1的元素,故使用链表,使主设备号为1的char_device_struct的next成员指向主设备号为256的char_device_struct
在这里插入图片描述
对于cdev结构体,其内容如下

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
} __randomize_layout;

cdev_alloc()函数,该函数为cdev结构体分配内存空间,并初始化list以及kobj成员
对于kobj目前我没搞清楚,在这个时候看到了一篇文章,写得不错,有兴趣的去看篇文章吧

老版本老内核的注册字符设备的方法
模版例子1

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define CHRDEVBASE_MAJOR      200           //主设备号
#define CHRDEVBASE_NAME     "chrdevbase"    //名字

static char readbuf[100]; /*读缓冲 */
static char writebuf[100];  /* 写缓冲 */
static char kerneldata[] = {"kernel data!"};


static int chrdevbase_open(struct inode *inode, struct file *filp)
{
   // printk("chrdevbase_open\r\n");
    return 0;
}

static int chrdevbase_release(struct inode *inode, struct file *filp)
{
   // printk("chrdevbase_release\r\n");
    return 0;   
}

static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count,
			loff_t *ppos)
{ 
    int ret  = 0;
    //printk("chrdevbase_read\r\n");
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    ret = copy_to_user(buf, readbuf, count);
    if(ret == 0) {

    } else {


    }


    return 0;  
}

static ssize_t chrdevbase_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
    int ret = 0;
    //printk("chrdevbase_write\r\n");
    ret = copy_from_user(writebuf, buf, count);
    if(ret == 0) {
        printk("kernel recevdata:%s\r\n", writebuf);
    } else {
 
    }


    return 0; 
}

/*
 * 字符设备 操作集合
 */
static struct file_operations chrdevbase_fops={
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .release = chrdevbase_release,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
};


static int __init chrdevbase_init(void)
{
    int ret = 0;
    printk("chrdevbase_init\r\n");

    /* 注册字符设备 */
    ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    if(ret < 0) {
        printk("chrdevbase init failed!\r\n");
    }

	return 0;
}

static void __exit chrdevbase_exit(void)
{
    printk("chrdevbase_exit\r\n");	
    /* 注销字符设备 */
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);

}

/*
 模块入口与出口
 */
module_init(chrdevbase_init);  /* 入口 */
module_exit(chrdevbase_exit);  /* 出口 */

MODULE_LICENSE("GPL");      
MODULE_AUTHOR("zuozhongkai");

将代码编译成ko文件,字符设备注册完并不会自动生成设备节点,设备节点需要手动创建。要想字符设备生成设备节点,可以使用mknod命令创建一个设备节点,

格式为:

mknod 名称 类型 主设备号 次设备号

例如
mknod /dev/test c 247 0
可以看到已经生成设备节点了,主设备号是247,次设备号是0
在这里插入图片描述

2、例子2,点灯led

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define LED_MAJOR   200
#define LED_NAME    "led"

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE              (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE      (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE      (0X020E02F4)
#define GPIO1_DR_BASE               (0X0209C000)
#define GPIO1_GDIR_BASE             (0X0209C004)

/* 地址映射后的虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF  0       /* 关闭 */
#define LEDON   1       /* 打开 */


/* LED灯打开/关闭 */
static void led_switch(u8 sta)
{
    u32 val = 0;

    if(sta == LEDON) {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);            /* bit3清零,打开LED灯 */
        writel(val, GPIO1_DR); 
    } else if(sta == LEDOFF) {
        val = readl(GPIO1_DR);
        val |= (1 << 3);            /* bit3清零,打开LED灯 */
        writel(val, GPIO1_DR);
    }
}

static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
    int retvalue;
    unsigned char databuf[1];

    retvalue = copy_from_user(databuf, buf, count);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    /* 判断是开灯还是关灯 */
    led_switch(databuf[0]);

    return 0;
}            


/* 字符设备操作集 */
static const struct file_operations led_fops = {
   	.owner	= THIS_MODULE,
	.write	= led_write,
	.open	= led_open,
	.release= led_release,
};


/* 入口 */
static int __init led_init(void)
{
    int ret = 0;
    unsigned int val = 0;
    /* 1,初始化LED灯,地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    /* 2,初始化 */
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);  /* 先清除以前的配置bit26,27 */
    val |= 3 << 26;     /* bit26,27置1 */
    writel(val, IMX6U_CCM_CCGR1);
 
    writel(0x5, SW_MUX_GPIO1_IO03);     /* 设置复用 */
    writel(0X10B0, SW_PAD_GPIO1_IO03);  /* 设置电气属性 */

    val = readl(GPIO1_GDIR);
    val |= 1 << 3;              /* bit3置1,设置为输出 */
    writel(val, GPIO1_GDIR);

    val = readl(GPIO1_DR);
    val |= (1 << 3);            /* bit3置1,关闭LED灯 */
    writel(val, GPIO1_DR);


    /* 1,注册字符设备 */
    ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if(ret < 0) {
        printk("register chardev failed!\r\n");
        return -EIO;
    }

    printk("led_init\r\n");
    return 0;
}

/* 出口 */
static void __exit led_exit(void)
{
    unsigned int val = 0;
    val = readl(GPIO1_DR);
    val |= (1 << 3);            /* bit3清零,打开LED灯 */
    writel(val, GPIO1_DR);

    /* 1,取消地址映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    /* 注销字符设备 */
    unregister_chrdev(LED_MAJOR, LED_NAME);
    printk("led_exit\r\n");
}

/*注册驱动加载和卸载*/
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

三、Linux 设备号,设备节点,以及新的注册方法

1、设备号
dev_t类型,内核用设备号来区分不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:

1、主设备号:占高12位,用来表示驱动程序相同的一类设备
2、次设备号:占低20位,用来表示被操作的哪个具体设备

假如有很多个同类设备如,led1,led2.led3,对led类分配一个主设备号,不同led,分配次设备号来标记。

设备节点
Linux把设备文件统一放在/dev目录内,内核加载注册驱动后,会在/dev目录,生成设备节点,应用程序打开一个设备时,是打开挂载在dev目录下设备文件节点,内核通过设备号来查找定位内核中管理的设备。用 ls -l命令可以看到如下的显示:sda,sda1是磁盘设备的节点,8就是主设备号,后面的0,1 ,2 就是次设备号,有好几个分区。
在这里插入图片描述

1.1 、静态分配一个设备号
第一种:静态分配一个设备号,使用的函数是:在include/linux/fs.h

extern int register_chrdev_region(dev_t, unsigned, const char *);

注意:使用该函数需要明确知道系统里面有哪些设备号没有用,剩下的才可以用

参数:
第一个:设备号的起始值。类型是dev_tdev_t是用来保存设备号的,是一个32为数。高12位用来保存主设备号,低12位用来保存次设备号	
第二个:次设备号的个数。
第三个:设备的名称。
返回值:成功返回0,失败返回小于0

Linux提供了几个宏定义来操作设备号:在include/linux/kdev_t.h

#define MINORBITS	20 //次设备号的位数,一共20位
define MINORMASK	((1U << MINORBITS) - 1) //此设备号的掩码
#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))//在dev_t里面获取主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))//在dev_t里面获取次设备号

//将主设备号和次设备号组成一个dev_t类型,第一个参数是主设备号,第二个参数的次设备号。
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

1.2 、动态分配一个设备号
第二种方法:动态分配,使用的函数是:

extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
使用动态分配会优先使用255234。
参数:
第一个:保存生成的设备号。
第二个:请求的第一个次设备号,通常是0
第三个:连续申请的设备号个数。
第四个:设备名称。
返回值:成功返回0,失败返回负数。

1.3 、如何注销设备号

extern void unregister_chrdev_region(dev_t, unsigned);//在include/linux/fs.h
参数:
第一个:分配设备号的起始地址
第二个:申请的连续设备号的个数

2、cdev结构体与file_operations结构体
设备号是设备的身份标识,那么cdev结构体就是字符设备本体了。开发者需要主动构造cdev结构体去描述一个设备。file_operations结构体则相当于行为能力的集合,用于具体操作设备之用。

驱动框架实际都是面向对象思维的。任何一种框架都会有一个代表设备的数据结构对象。字符设备驱动是struct cdev代表设备对象;platform虚拟总线框架是struct platform_device来代表设备对象;i2c总线框架是struct i2c_client代表设备对象;input框架则是struct input_dev代表设备对象。

struct cdev与struct file_operations
cdev原型:

#include  <linux/cdev.h>

struct cdev
{
 	struct kobject kobj;                  //相当于父类,表示该类型实体是一种内核对象
	struct module *owner;                 //填THIS_MODULE,表示该字符设备从属于哪个内核模块
 	const struct file_operations *ops;    //指向空间存放着针对该设备的各种操作函数地址
 	struct list_head list;                //链表指针域,各个cdev通过该链表指针串起来
 	dev_t dev;                            //设备号
	unsigned int count;                   //设备数量
};

**
c++中,有个一个类class,描述一类里面包括了各种数据,方法。
在内核里,每一个设备都有一个对应的cdev结构体,所有的结构体在内核中以链表的形式串接起来,结构体中的 struct list_head list成员就是链表的指针域,在初始化时,由系统完成链表的挂接。**

cdev结构体代表了设备本体,需要开发者在驱动模块初始化阶段手动去构造,并通过操作函数去初始化和挂接到链表。

file_operations原型

#include <linux/fs.h>

struct file_operations 
{
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,  loff_t len);
	int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

常用成员指针函数简单介绍:

llseek():用来修改一个文件的当前读写位置,并将新的位置进行返回,如果出错,则函数返回一个负值;
read():用来从设备中读取数据,成功时返回读取到的字节数,出错时,则函数返回一个负值;
write():用于向设备发送数据,成功时返回写入的字节数,若该函数未实行时,用户进行函数调用,将得到-EINVAL返回值;
unlocked_ioctl():提供设备相关的控制命令的实现(不是读和写操作),调用成功时,返回给调用程序一个非负值,与应用程序调用fcntl和ioctl函数相对应;
mmap():函数将设备内存映射到进程的虚拟地址空间中,当设备驱动未实现此函数时,用户进行调用将会得到-ENODEV返回值;
open():用于打开驱动设备,若驱动程序中不实现此函数,则设备的打开操作永远成功;
release():与open相反,用于关闭设备;
poll():一般用于询问设备是否可被非阻塞地立即读写;
aio_read():对文件描述符对应的设备进行异步读操作;
aio_write():对文件描述符对应的设备进行异步写操作。
file_operations结构体如上所示,其成员几乎全部是函数指针,这些函数指针所指向的操作函数需要由开发者编写,完成直接操作设备的能力。而这些能力又是与系统调用接口open()、read()、write()、close()等一一对应的。

struct module *owner; 填THIS_MODULE,表示该结构体对象从属于哪个内核模块
file_openations结构体需要开发者在驱动模块的初始化环节完成手工构建

2.1cdev相关操作函数
关联头函数 <include/linux/cdev.h>

1、cdev_init()函数
cdev_init()的作用用来初始化一个cdev结构体,函数的代码如下所示:类似c++中,初始化一个对象,实例。

#include <linux/cdev.h>
void cdev_init(struct cdev *cdev , const struct file_operations *fops);

参数:
cdev:要初始化化的cdev结构体
fops:设备的file_operations结构体
返回值: 无

2.2、 cdev_alloc()
cdev_alloc()的作用是用来动态分配一个cdev结构体,类似c++中,初始化一个对象,实例。,函数的代码如下所示:

#include <linux/cdev.h>
struct cdev *cdev_alloc(void);

参数: 无
返回值:
成功:返回cdev结构体的指针
失败:返回NULL

2.3 、cdev_put()
cdev_put()函数用于释放字符设备(char device)对象。其定义如下:

#include <linux/cdev.h>
void cdev_put(struct cdev *p)

参数:
p:cdev结构体指针
返回值: 无

在一个字符设备的生命周期中,其对象的引用计数会根据以下情况增加和减少:
调用cdev_init()初始化字符设备对象时,其引用计数为1。
调用cdev_add()将字符设备添加到系统中时,其引用计数加1。
打开该字符设备对应节点的进程数加到其引用计数。
关闭字符设备节点或调用cdev_del()将其从系统中删除时,其引用计数减1。
调用cdev_put() 减少其引用计数。
所以,cdev_put()函数主要用于手动释放对字符设备对象的引用,一般在不再需要使用该字符设备对象时调用,比如在模块退出时Force卸载该模块注册的字符设备。

2.4 、cdev_add()
cdev_add()函数用于向系统添加一个cdev,完成字符设备的注册,函数的代码如下所示:

#include <linux/cdev.h>
int cdev_add(struct cdev *p , dev_t dev , unsigned int  count);

参数:
p:字符设备的cdev结构体指针
dev:此设备负责的第一个设备号
count:与此对应的次设备号的数量
返回值:
成功:返回0
失败:返回error号

2.5 、cdev_del()
cdev_del()向系统删除一个cdev,用于完成字符设备的注销,函数的代码如下所示:

#include <linux/cdev.h>
void cdev_del(struct cdev *);

参数:
p:要在系统中移除的cdev结构体指针
返回值: 无

三、新的cdev字符设备注册驱动模板

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

static dev_t devno;  //设备号变量
int major, minor;    //主设备号,次设备号变量
struct cdev  my_dev;  //cdev设备描述结构体变量

int my_open(struct inode *pnode , struct file *pf);  //函数声明
int my_close(struct inode *pnode , struct file *pf);  //函数声明
struct file_operations fops={
    .open = my_open,
    .release = my_close,
};
static int __init my_init(void)
{
    int unsucc =0;
    
   //分配主次设备号
    unsucc = alloc_chrdev_region(&devno , 0 , 1 , "arch-char");
    if (unsucc){
        printk(" creating devno  faild\n");
        return -1;
    }
    major = MAJOR(devno);
    minor = MINOR(devno);
    
    /*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
    /*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
    
    cdev_init(&my_dev , &fops);
    my_dev.owner = THIS_MODULE;
 
    /*4、注册cdev结构体到内核链表中*/
    unsucc = cdev_add(&my_dev,devno,1);
    if (unsucc){
        printk("cdev add aild \n");
        return 1;
    }
    
    printk("the driver arch-char initalization completed\n");
    return 0;
}

static void  __exit my_exit(void)
{
    cdev_del(&my_dev);
    unregister_chrdev_region(devno , 1);
    printk("***************the driver arch-char exit************\n");
}
int my_open(struct inode *pnode , struct file *pf)
{
    printk("arch-char is opened\n");
    return 0;
   
}

int my_close(struct inode *pnode , struct file *pf)
{
    printk("arch-char is closed \n");
    return 0;
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

开发者在这模板基础上只需要把注意力放在file_operations的成员函数的具体实现上即可。也就是那5个步骤里,真正需要开发者关心的是第5步,其它基本可以照抄,除非你要改变变量名称。

my_open()函数是与应用层内核调用open()函数一一对应的,也即内核调用open()函数后,到了内核底层实际是调用了my_open()函数完成对设备的操作。而如何操作设备,完成这个open的行为,这就因不同设备而异。这在后面的例子中会继续详细说明。

2.6、struct inode 及 struct file

struct inode在<linux/fs.h>头文件里定义的struct inode结构体如下,其与磁盘上的i-node相对应。当应用层用open()访问一个文件时,会在内核中为i-node创建一个副本,主要记录如下内容。这是管理一个文件的基本要素。其中与字符驱动密切相关的是i_rdev成员,存放了设备文件对应的设备号。i_cdev则是存放着字符设备对应的cdev结构体指针,该结构体由驱动程序模块建立的。
在这里插入图片描述
在<linux/fs.h>头文件中定义的struct file结构体如下,和inode一样,其在文件被open()时,由系统创建。并获得由字符驱动模块构建的实际操作文件的函数入口file_operations结构体的指针,将指针存于f_op成员内。
在这里插入图片描述
在应用层,每个进程都会有一个文件描述符表,这个表内会存放进程打开的每一个文件的所谓文件描述符fd。实质,文件描述符表是一个数组,而fd则是这个数组元素的下标,也就是,如果fd = 0,则‘0’是指的该数组的第0个元素。fd=10,指的是该数组的第10个元素,该元素所存的内容是每个对应文件的struct file结构体指针,该结构体又存有文件的inode与file_operations结构体指针。
这就达到一个目的,当应用的任何一个操作设备文件的指令,如read(fd) , write(fd)等,都可以通过文件描述符表数组的fd下标对应的元素找到内核 的file_operations结构体指针,这样就可以调用该结构体内对应.read()和.write()的成员函数指针,从而完成实质的对字符设备的读,写操作。

字符设备驱动框架总结
在这里插入图片描述

5个数据类型,devno , struct cdev , struct file_operations是在驱动程序加载时创建的,与设备驱动是一一对应的,一个设备驱动对应一套这些数据对象。
struct inode 与 struct file 是在应用层open()设备文件时,创建的。

它们之间的关系如下
在这里插入图片描述
每个驱动有一个cdev,每个设备有一个inode

创建字符设备的三种方法

1. 使用早期的register_chardev()方法,基本上不这么用了

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<asm/uaccess.h>

int init_module(void);
#define DEVICE_NAME "chardev"

static int major;

static struct file_operations fops =
{
        .read = device_read,
        .write = device_write,
        .open = device_open,
        .release = device_release,
};

int init_module(void)
{
        major = register_chrdev(0, DEVICE_NAME, &fops);
        if (major < 0)
        {
                printk(KERN_ALERT "Registering char device failed with %d\n", major);
                return major;
        }
        printk(KERN_INFO "I was assigned major number %d.\n", major);
        return SUCCESS;
}

2. 使用cdev的方法

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>

int init_module(void);
#define DEVICE_NAME "chardev"

static int major;
static struct cdev *my_cdev;

static struct file_operations fops =
{
        .read = device_read,
        .write = device_write,
        .open = device_open,
        .release = device_release,
};
int init_module(void)
{
        int err;
        dev_t devid ;
        alloc_chrdev_region(&devid, 0, 1, "chardev");
        major = MAJOR(devid);
        my_cdev = cdev_alloc();
        cdev_init(my_cdev, &fops);
        err = cdev_add(my_cdev, devid, 1);
        if (err)
        {
                printk(KERN_INFO "I was assigned major number %d.\n", major);
                return -1;
        }
        printk("major number is %d\n", MAJOR(devid));
        return SUCCESS;
}

3. 使用udev在/dev/下动态生成设备文件的方式

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/pci.h>
#include<linux/moduleparam.h>
#include<linux/init.h>
#include<linux/string.h>
#include<asm/uaccess.h>
#include<asm/unistd.h>
#include<asm/uaccess.h>

int init_module(void);
#define DEVICE_NAME "chardev"

static int major;

static struct cdev *my_cdev;
static struct class *my_class;
dev_t devid ;

static struct file_operations fops =
{
        .read = device_read,
        .write = device_write,
        .open = device_open,
        .release = device_release,
};
int init_module(void)
{
        int err;
        alloc_chrdev_region(&devid, 0, 1, "chardev");
        major = MAJOR(devid);
        my_cdev = cdev_alloc();
        cdev_init(my_cdev, &fops);
        my_cdev->owner = THIS_MODULE;
        err = cdev_add(my_cdev, devid, 1);
        if (err)
        {
                printk(KERN_INFO "I was assigned major number %d.\n", major);
                return -1;
        }
        my_class = class_create(THIS_MODULE, "chardev_class1");
        if (IS_ERR(my_class))
        {
                printk(KERN_INFO "create class error\n");
                return -1;
        }
        class_device_create(my_class, NULL, devid, NULL, "chardev" "%d", MINOR(devid));//会在/sys/class/目录下生成类名
        printk("major number is %d\n", MAJOR(devid));
        return SUCCESS;
}

4、一般是这样写,如果有手动分配,就用自定义的,如果没有,就系统自动分配,并在/dev,/sys/class自动创建设备节点,

#define KEY_CNT     1
#define KEY_NAME    "key"

dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class *class;
struct device *device;

static int init_module(void)
{

    int ret = 0;

    /* 注册字符设备驱动 */
     major = 0;
    if(major) { /* 给定主设备号 */
      devid = MKDEV(major, 0);
       ret = register_chrdev_region(devid, KEY_CNT, KEY_NAME);
    } else {            /* 没给定设备号 */
        ret = alloc_chrdev_region(&devid, 0, KEY_CNT, KEY_NAME);
       major = MAJOR(devid);
       minor = MINOR(devid);
       printk("key major = %d, minor = %d\r\n",major, minor);
    }

    /* 2,初始化cdev */
    cdev.owner = THIS_MODULE;
    cdev_init(&cdev, &key_fops);

    /* 3,添加cdev */
    ret = cdev_add(&cdev, devid, KEY_CNT);
 

    /* 4、创建类 */
   class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(class)) {
        ret = PTR_ERR(class);
        goto fail_class;
    }

    /* 5,创建设备  */
   device = device_create(class, NULL, devid, NULL, KEY_NAME);
    if(IS_ERR(device)) {
        ret = PTR_ERR(device);
        goto fail_device;
    }
}

总结,这四种写法,第一,二是需要手动mknode创建设备节点的,后面的两种是不需要的,动态分配设备号,并在/dev,/sys/class,创建设备节点,我有一篇文章,/proc/device 与/dev,/sys/class,设备节点的区别,自己去看

 my_class = class_create(THIS_MODULE, "chardev_class1");
 class_device_create(my_class, NULL, devid, NULL, "chardev" "%d", MINOR(devid));

只有这样设置后,在/dev目录下,才有这些节点,如果不这样做,需要mknode命令,来创建节点,这种非常不方便

结果分析
1、静态分配的方法
首先需要使用cat /proc/devices需要明确知道系统里面有哪些设备号没有用,发现9可以使用,加载ko的时候使用静态分配的方法向驱动传入
这种不会在/dev下生产节点,需要mknode命令,手动创建
在这里插入图片描述
cat /proc/devices看看设备号是否生成,可以看到已经生成:
在这里插入图片描述
2、动态分配
系统分配一个未使用的设备号,cat /proc/devices看看设备号是否生成,可以看到已经生成
在这里插入图片描述

3、手动创建设备节点
注意字符设备注册完并不会自动生成设备节点,设备节点需要手动创建,所以需要用别的方法。要想字符设备生成设备节点,可以使用mknod命令创建一个设备节点,

格式为:

mknod 名称 类型 主设备号 次设备号
mknod /dev/test c 247 0

可以看到已经生成设备节点了,应用层可以使用文件IO接口对其进行读写等操作。
在这里插入图片描述
4、如何自动创建设备节点
在上面的Linux实验中使用insmod命令加载模块后,还需要通过mknod命令来收到创建设备节点,这样使用起来太麻烦了,并且不可能每个设备都去这样操作,Linux系统的存在就是为了方便使用,所以我们来看一下如何实现自动创建设备节点,当加载模块时,在/dev目录下自动创建相应的设备文件。

1 怎么自动创建一个设备节点?
在嵌入式linux中使用mdev来实现设备节点文件的自动创建和删除。

2 什么是mdev?
mdev是udev的简化版本,是busybox中所带的程序,最适合用在嵌入式系统。

3 什么是udev?
udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev一般用在PC上的linux中,相对mdev来说要复杂些。

4 怎么自动创建设备节点?
自动创建设备节点分为两个步骤:

3.4.1 步骤一:使用class_create函数创建一个class的类
3.4.2 步骤二:使用device_create函数在我们创建的类下面创建一个设备。

其实上面,已经说过了,例子如上面
可以发现不仅在/dev/下生成设备节点,也在/sys/class/目录下生成类名
在这里插入图片描述

四、总结,驱动框架图
在这里插入图片描述

例子1,多个相同类型设备
简单驱动代码例子,注意,本例子,网上找的,没有加入class,不会自动创建/dev设备节点,按前面的介绍,加进去就可以
分别初始化3个cdev结构体,分别关联cdev结构体与file_operations.、注册cdev结构体到内核链表中,这样,多个相同类型设备,主设备号一样,次设备号不一样

/*************************************************************************
	> File Name: muti-oper-mem.c
    用一个内存池来虚拟设备,驱动模块完成对内存池的读与写。应用层程序通过系统调用open
    read write操作设备muti0,muti1,muti2。

    本例用于驱动同一类设备的不同子设备。主设备号相同,次设备号不同。
    与单一设备驱动不同之处在以下几点:
    1、mem设备要对应有多个
    2、cdev设备对象一一对应
    3、devno统一用alloc_chrdev_region()生成
    4、每一个cdev设备都要cdev_init和cdev_add
    4、每一个cdev都要cdev_del()
 ************************************************************************/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h>


/*1、定义重要的变量及结构体*/

#define  MEM_SIZE 500    //每个内存块的大小500 byte
#define  DEVICE_NUM 3    //定义3个同类设备

dev_t devno;
struct mem_dev_t{
    struct cdev  my_dev;  //cdev设备描述结构体变量
    char  mem[MEM_SIZE]; //内存池,当成虚拟设备

};

/*这个mem_dev_t指针类型,可以指向数组,在这里是指向三个元素的首元素的地址。*/
struct mem_dev_t *mem_dev;  


/*所有驱动函数声明*/
loff_t llseek (struct file *, loff_t, int);
ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
ssize_t aio_read (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t aio_write (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int iterate (struct file *, struct dir_context *);
unsigned int poll (struct file *, struct poll_table_struct *);
long unlocked_ioctl (struct file *, unsigned int, unsigned long);
long compat_ioctl (struct file *, unsigned int, unsigned long);
int mmap (struct file *, struct vm_area_struct *);
int open (struct inode *, struct file *);
int flush (struct file *, fl_owner_t id);
int release (struct inode *, struct file *);
int fsync (struct file *, loff_t, loff_t, int datasync);
int aio_fsync (struct kiocb *, int datasync);
int fasync (int, struct file *, int);
int lock (struct file *, int, struct file_lock *);
ssize_t sendpage (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long get_unmapped_area(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int check_flags(int);
int flock (struct file *, int, struct file_lock *);
ssize_t splice_write(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t splice_read(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int setlease(struct file *, long, struct file_lock **);
long fallocate(struct file *file, int mode, loff_t offset,  loff_t len);
int show_fdinfo(struct seq_file *m, struct file *f);

//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
    .open = open,
    .release = release,
    .read = read,
    .write = write,
};

/*3、初始化 cdev结构体的函数,并将cdev结构体与file_operations结构体关联起来*/
/*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
/*为什么要用这个cdev_setup函数,是因为
*  1、如果是要创建多个cdev的话,就需要不同的数据结构体,在my_init可以看到这个结构体是mem_dev,实际是一个数组,
*  看mem_dev = kzalloc(sizeof(struct mem_dev_t) * DEVICE_NUM , GFP_KERNEL);的内存分配指令可以看出,
* 分配的空间是乘以了DEVICE_NUM了。有几个设备就分配了几个空间,连续分布后组成了数组。调用cdev_setup时,传入的实际只是mem_dev[n],即一个元素
* 2、cdev_setup中的操作都 是每个子设备都相同的操作,所以放到这个子函数里来*/
* 
static int cdev_setup(struct mem_dev_t *mem_dev , int num ){
    int unsucc = 0;
    dev_t sdevno = MKDEV(MAJOR(devno),num);    //根据传入的不同的子设备号,重新生成设备号
    cdev_init(&mem_dev->my_dev , &fops);

    mem_dev->my_dev.owner = THIS_MODULE;

    /*4、注册cdev结构体到内核链表中*/
    unsucc = cdev_add(&mem_dev->my_dev , sdevno , 1);
    if (unsucc){
        printk("cdev [%d] add failed \n",num);
        return -1;
    }
    printk("the major of this devno is %d\n",MAJOR(sdevno));
    printk("the minor of this devno is %d\n",MINOR(sdevno));

    return 0;
}

static int __init my_init(void){
    int major ;
    int i =0;
    int unsucc =0;
    mem_dev = kzalloc(sizeof(struct mem_dev_t) * DEVICE_NUM , GFP_KERNEL);
    if (!mem_dev){
        printk(" allocating memory   failed");
        return  -1;
    }

    /*2、创建 devno */
    unsucc = alloc_chrdev_region(&devno , 0 , DEVICE_NUM , "operate_memory");
    if (unsucc){
        printk(" creating devno    failed\n");
        return -1;
    }else{
        major = MAJOR(devno);
        printk("make devno,major = %d  ; \n",major);
    }
    
   3、 分别初始化3个cdev结构体,分别关联cdev结构体与file_operations.
  4、注册cdev结构体到内核链表中
    for ( i = 0 ; i < DEVICE_NUM ; i++){
        if (cdev_setup(mem_dev+i , i ) == 0){
            printk("the driver operate_memory  initalization is complete\n");               
        } else {
            printk("the driver operate_memory initalization failed\n");
            return -1;
        }
    }
    return 0;
}


static void  __exit my_exit(void)
{
    int i=0;
    for ( i = 0; i < DEVICE_NUM; i++){
        cdev_del(&((mem_dev+i)->my_dev));
    }
    unregister_chrdev_region(devno , DEVICE_NUM);
    printk("***************the driver operate_memory exit************\n");
}


/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/

int open(struct inode *pnode , struct file *pf){
    struct mem_dev_t *p = container_of(pnode->i_cdev, struct mem_dev_t , my_dev);
    pf->private_data = p;  //把全局变量指针放入到struct file结构体里
    printk("operate_memory is opened\n");
    return 0;
}


/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){
    printk("operate_memory is closed \n");
    return 0;
}
    
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");

/*file_operations结构全成员函数.read的具体实现*/
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){
    struct mem_dev_t *pdev = pf->private_data;
    int count = 0;
    //判断偏移量的有效性
    if (*ppos >= MEM_SIZE){
        return 0;
    }
    //判断能够读到的字节数量
    if  (size > MEM_SIZE - *ppos){
        count = MEM_SIZE - *ppos;
    }else{
        count = size;
    }

    //copy_from_user返回值大于0失败
    if ( copy_to_user(buf , &pdev->mem[*ppos] , count )){
        return 0;
    }else{
        *ppos += count;
        return count;
    }
        
}

/*file_operations结构全成员函数.write的具体实现*/
ssize_t write (struct file * pf, const char __user *buf, size_t size , loff_t *ppos){
    struct mem_dev_t *pdev = pf->private_data;
    int count = 0;
    //判断偏移量的有效性
    if (*ppos >=MEM_SIZE ){
        return 0;
    }
    //判断能够写入的字节数量
    if (size > MEM_SIZE-*ppos){
        count = MEM_SIZE-*ppos;
    }else{
        count = size;
    }
    //copy_from_user返回值大于0失败
    if ( copy_from_user(&pdev->mem[*ppos] , buf , count)){
        return 0;
    }else{
        *ppos +=count;
        return count;
    }

}

应用层测试代码

/*************************************************************************
	> File Name: muti_op_mem.c
    对应驱动的应用层测试程序。调用open read write 等 来测试驱动的运行
    分别打开三个驱动进行读写,测试是否正常 

    执行:./muei_op_mem.elf /dev/muti0 /dev/muti1 dev/muti2
************************************************************************/

#include<stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>

#define DEVICE_NUM 3

int  main(int argc , char **argv){

    int fd[DEVICE_NUM] = {0};
    int size = 0;
    char buf[20] = {0};
    char * mesg[3] ={"this is test0\n" , "this is test1\n" , "this is test2\n"};
    int pos = 0;
    int i=0;

    if  (argc < DEVICE_NUM+1){
        printf("argument is  less!\n");
        return 0;
    }
    //向设备写入数据
    for (i = 0; i < DEVICE_NUM; i++){
        printf("open file %s\n",argv[i+1]);
        fd[i] = open(argv[i+1] , O_RDWR|O_APPEND  );
        if (fd[i] < 0){
            perror("open ");
        }

        size = write(fd[i] ,mesg[i] , strlen(mesg[i]));
        printf("write device[%d] size = %d\n",i,size);
        if (size == strlen(mesg[i])){
            printf("write to device[i] is sruccessul\n",i);
        }else{
            printf("write to device[i] is failed\n",i);
        }
    
        sleep(1);
        //printf("sleep is end \n");
        close(fd[i]);
    
        //从设备读出数据
        fd[i] = open(argv[i+1] , O_RDONLY);
        if (fd[i] < 0){
            perror("open");
        }

        size = read(fd[i] , buf , strlen(mesg[i]));
        if (size > 0){
            printf("read data of device[%d] is : %s\n" ,i, buf);
        }else{
            printf("read data of device[%d]  failed\n",i);
        }

        sleep(1);
        //printf("sleep is end \n");
        close(fd[i]);
    }
    return 0;

}

结果
在这里插入图片描述

2、例子2,新主流方法,自动创建class,设备节点,led字符设备驱动
在这里插入图片描述

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>


#define NEWCHRLED_NAME  "newchrled"
#define NEWCHRLED_COUNT 1

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE              (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE      (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE      (0X020E02F4)
#define GPIO1_DR_BASE               (0X0209C000)
#define GPIO1_GDIR_BASE             (0X0209C004)


/* 地址映射后的虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF  0       /* 关闭 */
#define LEDON   1       /* 打开 */



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

};

struct newchrled_dev newchrled; /* led设备 */

/* LED灯打开/关闭 */
static void led_switch(u8 sta)
{
    u32 val = 0;

    if(sta == LEDON) {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);            /* bit3清零,打开LED灯 */
        writel(val, GPIO1_DR); 
    } else if(sta == LEDOFF) {
        val = readl(GPIO1_DR);
        val |= (1 << 3);            /* bit3清零,打开LED灯 */
        writel(val, GPIO1_DR);
    }
}

static int newchrled_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &newchrled;
    return 0;
}

static int newchrled_release(struct inode *inode, struct file *filp)
{
    struct newchrled_dev *dev = (struct newchrled_dev*)filp->private_data;
    
    return 0;
}

static ssize_t newchrled_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
    int retvalue;
    unsigned char databuf[1];

    retvalue = copy_from_user(databuf, buf, count);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    /* 判断是开灯还是关灯 */
    led_switch(databuf[0]);

    return 0;
}

static const struct file_operations newchrled_fops = {
    .owner = THIS_MODULE,
    .write	= newchrled_write,
	.open	= newchrled_open,
	.release= newchrled_release,
};

/*入口 */
static int __init newchrled_init(void)
{
    int ret = 0;
    unsigned int val = 0;
    printk("newchrled_init\r\n");
    /* 1,初始化LED灯,地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    /* 2,初始化 */
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);  /* 先清除以前的配置bit26,27 */
    val |= 3 << 26;     /* bit26,27置1 */
    writel(val, IMX6U_CCM_CCGR1);
 
    writel(0x5, SW_MUX_GPIO1_IO03);     /* 设置复用 */
    writel(0X10B0, SW_PAD_GPIO1_IO03);  /* 设置电气属性 */

    val = readl(GPIO1_GDIR);
    val |= 1 << 3;              /* bit3置1,设置为输出 */
    writel(val, GPIO1_GDIR);

    val = readl(GPIO1_DR);
    val |= (1 << 3);            /* bit3置1,关闭LED灯 */
    writel(val, GPIO1_DR);

    newchrled.major = 0;    /* 设置为0,表示由系统申请设备号 */

    /* 2,注册字符设备 */
    if(newchrled.major){    /* 给定主设备号 */
        newchrled.devid = MKDEV(newchrled.major, 0);
        ret = register_chrdev_region(newchrled.devid, NEWCHRLED_COUNT, NEWCHRLED_NAME);
    } else {                /* 没有给定主设备号 */
        ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_COUNT, NEWCHRLED_NAME);
        newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MINOR(newchrled.devid);
    }
    if(ret < 0) {
        printk("newchrled chrdev_region err!\r\n");
        goto fail_devid;
    }
    printk("newchrled major=%d, minor=%d\r\n", newchrled.major, newchrled.minor);

    /* 3,注册字符设备 */
    newchrled.cdev.owner = THIS_MODULE;
    cdev_init(&newchrled.cdev, &newchrled_fops);
    ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_COUNT);
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 4,自动创建设备节点 */
    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.class)) {
        ret = PTR_ERR(newchrled.class);
		goto fail_class;
    }

    newchrled.device = device_create(newchrled.class, NULL,
			     newchrled.devid, NULL, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.device)) {
        ret = PTR_ERR(newchrled.device);
        goto fail_device;
    }
		
    return 0;

fail_device:
    class_destroy(newchrled.class);
fail_class:
    cdev_del(&newchrled.cdev);
fail_cdev:
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);
fail_devid:
	return ret; 
}

/* 出口 */
static void __exit newchrled_exit(void)
{
   
    unsigned int val = 0;
    printk("newchrled_exit\r\n");

    val = readl(GPIO1_DR);
    val |= (1 << 3);            /* bit3清零,打开LED灯 */
    writel(val, GPIO1_DR);

    /* 1,取消地址映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    /* 1,删除字符设备 */
    cdev_del(&newchrled.cdev);

    /* 2,注销设备号 */
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);

    /* 3,摧毁设备 */
    device_destroy(newchrled.class, newchrled.devid);

    /* 4,摧毁类 */
    class_destroy(newchrled.class);
}

/* 注册和卸载驱动 */
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

应用层测试代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


/*
 *argc:应用程序参数个数
 *argv[]:具体的参数内容,字符串形式 
 *./ledAPP  <filename>  <0:1> 0表示关灯,1表示开灯
 * ./ledAPP /dev/led 0    关灯
 * ./ledAPP /dev/led 1    开灯
 */

#define LEDOFF 0
#define LEDON 1

int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];


    if(argc != 3) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0) {
        printf("file %s open failed!\r\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]); /* 将字符转换为数字 */

    retvalue = write(fd, databuf, sizeof(databuf));
    if(retvalue < 0) {
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }

    close(fd);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值