Linux驱动开发——字符设备驱动

目录

0.前言

1.字符设备

1.1字符设备驱动框架

2.步骤

2.1 申请设备号

2.2 创建字符设备

2.3 初始化字符设备

2.4 将字符设备和设备号关联

2.5 exit

3.完整程序

4.测试程序

5.总结


0.前言

        分享一句话:大事随心,小事随脑。


1.字符设备

        Linux有三种驱动设备模型,字符设备、块设备、网络设备。

       字符设备是面向字节流的。定义:是指只能一个字节一个字节的读写的设备,不能随机的读取设备中的某一段数据。
        常见的字符设备:鼠标 键盘 串口 。。。


1.1字符设备驱动框架

init:
            {
				  申请设备号(静态申请 动态申请)
                  创建字符设备
                  初始化字符设备
				  将设备号和字符设备关联
            }

exit:
			{
				销毁字符设备
				删除申请的设备号
			}

        设备号:是一个32bits位的无符号数,高12位是主设备号,低20位是次设备号。

        主设备号标识设备对应的驱动程序,告诉Linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务。

        次设备号则用来标识具体且唯一的某个设备。


2.步骤

2.1申请设备号

        我以动态申请为例。

        函数:alloc_chrdev_region

        查看内核代码。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

参数:
    *dev:设备号指针
    baseminor:子设备的第一个编号
    count:子设备个数
    *name:设备名称

返回值:成功为0

2.2创建字符设备

        函数:cdev_alloc

        (我用的是source insight看的内核代码)

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;
}

无入参
返回值:指向字符设备结构体的指针

        在linux内核中,struct cdev用来描述一个字符设备。再来看一下这个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;                        //隶属于同一个主设备号的次设备个数
};

        所以我们要搞一个struct cdev指针接收,这是后话。


2.3初始化字符设备

        函数:cdev_init

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

参数:
    *cdev:被初始化的字符设备指针
    *fops:字符设备操作函数指针集
无返回值

        看下这个*fops:字符设备操作函数指针集。

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

        里面大都是函数指针,指向你去定义的函数。当然函数格式和这里面的函数一样。


2.4将字符设备和设备号关联

        函数: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;
}

参数:
    *p:字符设备结构体指针
	dev:设备号
	count:子设备个数
返回值:成功为0

2.5exit

        函数:cdev_del    销毁字符设备

                   unregister_chrdev_region    删除申请的设备号

void cdev_del(struct cdev *p)
{
	cdev_unmap(p->dev, p->count);
	kobject_put(&p->kobj);
}
void unregister_chrdev_region(dev_t from, unsigned count)
{
	dev_t to = from + count;
	dev_t n, next;

	for (n = from; n < to; n = next) {
		next = MKDEV(MAJOR(n)+1, 0);
		if (next > to)
			next = to;
		kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
	}
}

参数:
    from:设备号
	count:子设备个数

3.完整程序


/*动态 */
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h> 

#define BUF_SIZE 100

int g_min=0;			//子设备得第一个编号
dev_t g_DevNum=0;		//存放设备号
char *g_DevName="hehe";			//设备名  这是注册函数所需要的参数

struct cdev *g_pCdev=NULL;			//用来接收cdev_alloc()返回的内存空间

char g_Buf[BUF_SIZE]="Darling, you fly slowly";

int HelloOpen(struct inode *pNode,struct file *pFile) 
{
	printk("into hello open-----------\r\n");
	printk("leave hello open-----------\r\n");
	return 0;
} 


int HelloClose(struct inode *pNode,struct file *pFile)       
{
	printk("into hello close---------\r\n");
	printk("leave hello close------------\r\n");
	return 0;

}

ssize_t HelloRead(struct file *pFile,char __user *buf,size_t count,loff_t *pOffset)
{
	int ret=-1;
	printk("into hello read---------------\r\n");
	if(count>BUF_SIZE-1)
	{
		count=BUF_SIZE-1;
	}
	ret=copy_to_user(buf,g_Buf,count);
	if(ret)
	{
		printk("copy to usr error!\r\n");
		ret=-EFAULT;
		return ret;
	}
	printk("copy to user ok!,g_Buf=%s\r\n",g_Buf);

	printk("leave hello read-------------\r\n");
	return count;
}

struct file_operations stFops=				//这就是定义一个操作方法指针
{
	.owner=THIS_MODULE,						//就像C++  this指针
	.open=HelloOpen,	                    //指向HelloOpen函数   
	.read=HelloRead,	                    //指向HelloRead函数
	.release=HelloClose,		//关闭			
	//想用哪些方法就写哪些
};

int HelloModule(void)
{
	int ret=0;
	printk("into hello Module--------------\r\n");
	//1 register device number
	ret=alloc_chrdev_region(&g_DevNum,g_min,1,g_DevName);	//这是动态注册函数
							//取地址设备号  子设备得第一个编号  子设备个数  设备名
	if(0!=ret)
	{
		printk("register char device number error!\r\n");
		return ret;
	}
	printk("1:register char device number ok!\r\n");
    printk("major=%d minor=%d\r\n",MAJOR(g_DevNum),MINOR(g_DevNum));
								//这两个宏定义就是得到  主设备号和 次设备号
								
								
	// 2 创建字符设备
	g_pCdev=cdev_alloc();		//无入参
	if(NULL==g_pCdev)			//判断一下
	{
		printk("alloc char device error!\r\n");
		goto ALLOC_CDEV_ERR;		//这是干什么  就是申请失败 
		//那自然那个设备号没啥用  
		//不是销毁那个接受的指针(本来就是指空)  
		//销毁的是设备号   跳转到ALLOC_CDEV_ERR
	}
	printk("2:alloc char device ok!\r\n");
	
	
	//3 初始化设备
	cdev_init(g_pCdev,&stFops);			//第二个参数是一个方法指针    无返回值
	printk("3:init char device ok!\r\n");
	
	
	
	// 4 关联   创建的设备和设备号关联
	ret=cdev_add(g_pCdev,g_DevNum,1);		//还是用ret  可以明白
	if(0!=ret)
	{
		printk("cdev add to kernel error!\r\n");
		goto CDEV_ADD_ERROR;				//关联失败 CDEV_ADD_ERROR
	}
	printk("4:add char device to kernel ok!\r\n");

	printk("leave hello Module--------------\r\n");
	return 0;
CDEV_ADD_ERROR:
	cdev_del(g_pCdev);		//关联失败 那创建的设备也没用  删除指针  就是删除那片空间
ALLOC_CDEV_ERR:					//
	unregister_chrdev_region(g_DevNum,1);		//这就用来销毁的
	return -1;

}

void HelloExit(void)
{
	printk("into hello exit---------------\r\n");
	cdev_del(g_pCdev);							
	//退出时去删除这字符设备 入参是指针
	unregister_chrdev_region(g_DevNum,1);		//这个注销和静态是一样的
	printk("leave hello exit--------------\r\n");
}

module_init(HelloModule);
module_exit(HelloExit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("shixh");
MODULE_ALIAS("hehe");
MODULE_DESCRIPTION("giao");

        顺便提下这两个函数:copy_to_user和copy_from_user

        我们知道内核的数据用户是不能访问的,但是可以通过内核提供的函数。

内核到用户
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
	*to:用户空间指针
	*from:数据源--》内核空间指针
	n:拷贝的字节数
	返回值:成功为0
    
用户到内核
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
作用:将用户空间数据拷贝到内核空间
     *to:内核buf指针
     *from:用户空间数据源指针
     n:拷贝的字节数
     返回值:成功为0

4.测试程序

        上面那些做完相当于什么,我们已经成功安装了一个字符设备驱动,剩下的等待一个对应的字符设备接入。所以搞一个测试程序。

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

#define BUF_SIZE 100

int main()
{
	int fd=open("/dev/nami",O_RDWR);
	if(fd<0)
	{
		printf("open error!\r\n");
		return -1;
	}
	printf("open  ok!\r\n");
	
	char buf[BUF_SIZE]={0};
	read(fd,buf,BUF_SIZE-1);
	printf("read data:%s\r\n",buf);

	close(fd);
	return 0;
}

5.总结

        似乎也没什么是吧。

        真正等到工作时也差不多。我贴出来一个平台驱动给大家看看。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>

MODULE_LICENSE("GPL v2");

 #define SI7006_GET_TEMP _IOR ('S',0x00,short)
 #define SI7006_GET_HUM  _IOR ('S',0x01,short)


struct Si7006_device{
	dev_t devno;
	struct cdev cdev;
	struct class *cls;
	struct device *dev;
	struct i2c_client *client;
};

int  Si7006_read_temperature(struct Si7006_device *si)
{
	int ret;
	int  temp_code;
	//int temperature;
	struct i2c_msg msg[2];
	unsigned char rx_buf[2];
	struct i2c_client *client = si->client;
	unsigned char tx_buf[] = {0xE3};

	msg[0].addr  = 0x40;
	msg[0].flags = 0x0;
	msg[0].len   = sizeof(tx_buf)/sizeof(tx_buf[0]);
	msg[0].buf   = tx_buf;

	msg[1].addr  = 0x40;
	msg[1].flags = 0x1;
	msg[1].len   = sizeof(rx_buf)/sizeof(rx_buf[0]);
	msg[1].buf   = rx_buf;

	ret = i2c_transfer(client->adapter,msg,sizeof(msg)/sizeof(msg[0]));
	if(ret < 0){
		printk("Fail to i2c transfer\n");
		return ret;
	}

	temp_code = rx_buf[0] << 8 | rx_buf[1];
	//temperature = (175.72 * temp_code)/ 65536 -  46.85;

	return temp_code;	
}

int  Si7006_read_humidity(struct Si7006_device *si)
{
	int ret;
	int hum_code;
	//int humidity;
	struct i2c_msg msg[2];
	unsigned char rx_buf[2];
	struct i2c_client *client = si->client;
	unsigned char tx_buf[] = {0xE5};

	msg[0].addr  = 0x40;
	msg[0].flags = 0x0;
	msg[0].len   = sizeof(tx_buf)/sizeof(tx_buf[0]);
	msg[0].buf   = tx_buf;

	msg[1].addr  = 0x40;
	msg[1].flags = 0x1;
	msg[1].len   = sizeof(rx_buf)/sizeof(rx_buf[0]);
	msg[1].buf   = rx_buf;

	ret = i2c_transfer(client->adapter,msg,sizeof(msg)/sizeof(msg[0]));
	if(ret < 0){
		printk("Fail to i2c transfer\n");
		return ret;
	}

	hum_code = rx_buf[0] << 8 | rx_buf[1];
	//humidity = (125 * hum_code)/ 65536 - 6;

	return hum_code;
}



/* This function handles open for the character device */
static int Si7006_chrdev_open(struct inode *inode, struct file *file)
{
	struct Si7006_device *si7006 = container_of(inode->i_cdev,struct Si7006_device,cdev);
	file->private_data = si7006;
	return 0;
}

static long Si7006_chrdev_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
	int ret;
	unsigned int temp;
	unsigned int hum;
	unsigned int id;
	struct Si7006_device *si = file->private_data;
	
	switch(cmd){
		case SI7006_GET_TEMP:
			temp = Si7006_read_temperature(si);
			ret = copy_to_user((void __user *)arg,&temp,sizeof(temp));
			if(ret){
				printk("Fail to copy to user\n");
				return -EFAULT;
			}
			break;
	   case SI7006_GET_HUM:
	   		hum = Si7006_read_humidity(si);
			ret = copy_to_user((void __user *)arg,&hum,sizeof(hum));
			if(ret){
				printk("Fail to copy to user\n");
				return -EFAULT;
			}
			break;
			
		default:
			printk("unKnown cmd!\n");
			break;
	}
	
	return 0;
}

static int Si7006_chrdev_release(struct inode *inode, struct file *file)
{
	printk("si7006 chrdev release success!\n");

	return 0;
}


/* File operations struct for character device */
static const struct file_operations Si7006_fops = {
	.owner		= THIS_MODULE,
	.open		= Si7006_chrdev_open,
	.unlocked_ioctl	= Si7006_chrdev_ioctl,
	.release	= Si7006_chrdev_release,
};


int register_Si7006_chrdev(struct Si7006_device *si)
{
	int retval;

	cdev_init(&si->cdev,&Si7006_fops);

	retval = alloc_chrdev_region(&si->devno,0,1,"Si7006");
	if(retval < 0){
		printk("Fail to alloc chrdev region\n");
		goto err_alloc_chrdev_region;
	}
	
	retval = cdev_add(&si->cdev,si->devno,1);
	if(retval){
		printk("Fail to cdev add\n");

		goto err_cdev_add;
	}

	si->cls = class_create(THIS_MODULE, "Si7006");
	if (IS_ERR(si->cls)){
		printk("Fail to class create\n");
		retval = PTR_ERR(si->cls);
		goto err_class_create;
	}
	
	si->dev = device_create(si->cls,NULL,si->devno,NULL,"Si7006_device");
	if (IS_ERR(si->dev)) {
		printk("Fail to device create\n");
		retval = PTR_ERR(si->dev);
		goto err_device_create;
	}

	return 0;
	
err_device_create:
	class_destroy(si->cls);
	
err_class_create:
	cdev_del(&si->cdev);

err_cdev_add:
	unregister_chrdev_region(si->devno,1);

err_alloc_chrdev_region:
	return retval;	
}


static int Si7006_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
	int ret;
	struct Si7006_device *si;
	
	printk("Si7006 probe success\n");
	si = devm_kmalloc(&client->dev,sizeof(*si),GFP_KERNEL);
	if(!si){
		printk("Fail to kmalloc\n");
		return -ENOMEM;
	}
	i2c_set_clientdata(client,si);

	si->client = client;

	ret = register_Si7006_chrdev(si);
	if(ret < 0){
		printk("Fail to register Si7006 chrdev\n");
		return ret;
	}
	
	return 0;
}


static int  Si7006_remove(struct i2c_client *client)
{
	
	struct Si7006_device *si = i2c_get_clientdata(client);
	
	printk("Si7006 remove success\n");
	device_destroy(si->cls,si->devno);
	class_destroy(si->cls);
	cdev_del(&si->cdev);
	unregister_chrdev_region(si->devno,1);
	return 0;
}

static struct i2c_device_id  Si7006_id[] = {
	{ "Si7006", 0 },
	{ }
};

MODULE_DEVICE_TABLE(i2c, Si7006_id);

#ifdef CONFIG_OF
static struct of_device_id dt_match[] = {
	{ .compatible = "silabs,si7006", },
	{},
};
MODULE_DEVICE_TABLE(of,dt_match);
#endif /* CONFIG_OF */



static struct i2c_driver Si7006_driver = {
	.driver = {
		.name	= "Si7006",
		.owner	= THIS_MODULE,
		.of_match_table	= of_match_ptr(dt_match),
	},
	.probe		= Si7006_probe,
	.remove		= Si7006_remove,
	.id_table	= Si7006_id,
};

module_i2c_driver(Si7006_driver);


        祝大家身心愉悦!

相关文章

学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习...
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面...
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生...
Can’t connect to local MySQL server through socket \'/v...
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 ...
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服...