字符设备驱动模板

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>


/* 字符设备数量、名称、以及缓存大小 */
#define CHRDEV_CNT		1
#define CHRDEV_NAME		"chrdev"
#define BUF_SIZE		256

#define CHRDEV_MAGIC	'k'		//指令类型
#define CHRDEV_MAXNR	 3		//最大指令序号
/* ioctl 自定义指令 */			
#define CMD_OPEN		(_IO(CHRDEV_MAGIC, 1))
#define CMD_CLOSE		(_IO(CHRDEV_MAGIC, 2))
#define CMD_SET			(_IO(CHRDEV_MAGIC, 3))


/* 自定义字符设备结构体 */

struct chr_dev {
	dev_t devnum;			//设备号
	struct cdev *pcdev;		//cdev
	struct class *class;	//类
	struct device *device;	//设备
	char   kbuf[BUF_SIZE];	//数据存储区
};

static chr_dev firdev = {
	.devnum = 0,
};
	
 /*
  * @brief  文件打开函数
  * @param  inode : 传递给驱动的inode
			file  : 要打开的设备文件	
  * @retval 0 成功, 其他 失败
  */ 
  
static int chrdev_open(struct inode *inode, struct file *file)
{
	file->private_data = &firdev; 		//设置私有数据
//	firdev.devnum = inode->i_rdev;		//获取设备号
	printk(KERN_INFO "chrdev_open,devnum:%x\n", firdev.devnum);
	return 0;
}

/*
  * @brief  文件关闭函数
  * @param  inode : 传递给驱动的inode
			file  : 要关闭的设备文件	
  * @retval 0 成功, 其他 失败
  */ 
static int chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "chrdev_release\n");
	return 0;
}



/*
  * @brief  读函数, 将内核中的数据拷贝到应用层
  * @param  file: 要打开的设备文件
			buf : 返回给用户空间的数据缓冲区
			cnt : 要读取的数据长度
			offt: 相对于文件首地址的偏移	
  * @retval 读取的字节数, 负值表示读取失败
  */ 
ssize_t chrdev_read(struct file *filp, char __user *ubuf, size_t size, loff_t *ppos)
{
	int ret = -1;
	unsigned long p =  *ppos;
	unsigned int count = size;
	struct chr_dev *dev = filp->private_data;	//获取私有数据
	//有效长度判断
	 if (p >= BUF_SIZE)
		return 0;
	if (count > BUF_SIZE - p)
		count = BUF_SIZE - p;
	//一定要用如下拷贝函数, 从内核空间拷贝至用户
	if(copy_to_user(ubuf, (void*)(dev->kbuf + p), count)) {
		printk(KERN_ERR "copy_to_user fail\n");
		return -EINVAL;
	}
	else {
		*ppos += count;
		ret = count;
		printk(KERN_INFO "to user success...\n");
	}	
	return ret;
}



/*
  * @brief  写函数, 将应用层传递过来的数据复制到内核中
  * @param  filp: 打开的文件描述符
			buf : 要写给设备写入的数据
			cnt : 要写入的数据长度
			offt: 相对于文件首地址的偏移	
  * @retval 写入的字节数, 负值表示写入失败
  */ 
static ssize_t chrdev_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *ppos)
{
	int ret = -1;
	unsigned long p =  *ppos;
	unsigned int count = size;
	struct chr_dev *dev = filp->private_data;
	//有效长度判断
	if(p >= BUF_SIZE)
		return 0;
	if (count > BUF_SIZE - p)
		count = BUF_SIZE - p;
	//一定要用如下拷贝函数,从用户空间拷贝至内核
	if (copy_from_user(dev->kbuf + p, ubuf, count)) {
		printk(KERN_ERR "copy_from_user fail\n");
		return -EINVAL;
	}
	else {
		*ppos += count;
		ret = count;
		printk(KERN_INFO "from user success...\n");
	}
	return ret;
}


/*
  * @brief  文件定位函数
  * @param  filp: 打开的文件描述符
			whence: 偏移起始位置
			offset: 偏移的步数
  * @retval 偏移的字节数, 负值表示偏移失败
  */ 
static loff_t chrdev_llseek(struct file *filp, loff_t whence, int offset)
{
	unsigned int newpos = 0;

	switch(whence) {
		case SEEK_SET: newpos = offset; 				break;
		case SEEK_CUR: newpos = filp->f_pos + offset; 	break;
		case SEEK_END: newpos = BUF_SIZE -1 + offset;	break;
		default: return -EINVAL;
	}
	if ((newpos < 0) || (newpos > BUF_SIZE))
		return -EINVAL;
	
	filp->f_pos = newpos;
	
	return newpos;
}


/*
  * @brief  IO控制函数
  * @param  filp: 打开的文件描述符
			cmd: 命令
			arg: 参数
  * @retval 0表示执行成功, 负值表示失败
  */ 
long chrdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	if (_IOC_TYPE(cmd) != CHRDEV_MAGIC) 
		return -EINVAL;
  	if (_IOC_NR(cmd) > CHRDEV_MAXNR) 
		return -EINVAL;
		
	switch(cmd) {
		case CMD_OPEN:
			printk("IO open device!\n");
			return 0;
		case CMD_CLOSE:
			printk("IO close device!\n");
			return 0;
		case CMD_SET:
			printk("IO setup device, arg:%d\n", arg);
			return 0;
		default:
			return -EINVAL;
	}
	return 0;
}




//文件操作结构体, 外部操作此模块的接口, 需要我们填充
//.owner:指向拥有这个结构的模块的指针,用来在它的操作还在被使用时阻止模块被卸载
static struct file_operations chrdev_fops = {
	.owner 		= THIS_MODULE,
	//应用层间接调用的就是如下接口
	.open		= chrdev_open,		//打开设备时调用
	.release	= chrdev_release,	
	.write 		= chrdev_write,
	.read		= chrdev_read,
	.llseek		= chrdev_llseek,
	.unlocked_ioctl = chrdev_ioctl,
};

static int template_probe(struct platform_device *pdev)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int ret = 0;
	//自动生成设备节点
	
	//分配设备号 并注册进内核
	/*
		有缺陷, 这个函数分配的设备号会把当前主设备号的所有次设备号都占用
	*/
//	firdev.devnum = register_chrdev(0, "drvTemplate", &chrdev_fops);
//	if(firdev.devnum < 0){//分配失败
//		printk(KERN_ERR, "alloc_chrdev_region fail\n");
//		goto chrdev_fail:
//	}
	//新版本设备注册使用 : 
	//alloc_chrdev_region 申请设备, register_chrdev_region 注册设备
	//具体实现如下
/*
	int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
dev :alloc_chrdev_region函数向内核申请下来的设备号

baseminor :次设备号的起始

count: 申请次设备号的个数

name :执行 cat /proc/devices显示的名称
*/
	ret = alloc_chrdev_region(&firdev.devnum, 0, 1, "drvTemplate");
	if(IS_ERR(ret)){
		printk(KERN_ERR, "alloc_chrdev_region fail\n");
		goto chrdev_fail:
	}
	firdev.pcdev = cdev_alloc();
	if(IS_ERR_OR_NULL(firdev.pcdev)){
		printk(KERN_ERR, "cdev_alloc fail\n");
		goto cdev_alloc_fail:
	}
	
	cdev_init(firdev.pcdev, &chrdev_fops);
	firdev.pcdev->owner = THIS_MODULE;
	
	ret = cdev_add(firdev.pcdev, firdev.devnum, 1);
	if(IS_ERR(ret)){
		printk(KERN_ERR, "cdev_add fail\n");
		goto cdev_add_fail;
	}
	
	// 3.创建类
	// 注册字符设备驱动完成后, 添加设备类的操作, 让内核帮我们发信息
	// 给udev,让udev自动创建和删除设备文件

	firdev.class = class_create(THIS_MODULE, CHRDEV_NAME);
	if(IS_ERR(firdev.class)){
		goto class_fail;
	}


	// 4.创建设备
	// 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字
	// 所以我们这里要的文件名是/dev/firdev
	dev = device_create(firdev.class, NULL , firdev.devnum, NULL, CHRDEV_NAME);
	if (IS_ERR(dev)) {
		goto device_fail;
	}



	
device_fail:
	class_destroy(firdev.class);
//class_fail:
	//unregister_chrdev(firdev.devnum, "drvTemplate");
cdev_add_fail:
	cdev_del(*firdev.pcdev);
cdev_alloc_fail:
	unregister_chrdev_region(firdev.devnum, 1);
chrdev_fail:
	return -EINVAL;
}


static const struct of_device_id template_tree_node[] = {
    {.compatible = "template_demo_dev"}, //用于匹配设备树
    {},
};


static struct platform_driver template_driver = {
	.probe = template_probe, 	//与平台设备匹配上之后会调动这个函数
	.remove = template_remove, 	//退出时要释放资源
	.driver = {
			.name = "template_demo_drv",
			.of_match_table = template_tree_node,//匹配设备树中的设备必须要指定这个值
	},
};


//模块加载函数
static int __init chrdev_init(void)
{
	printk("drvTemplate__init__\n");

	int err;
	err = platform_driver_register(&template_driver);
	return err;
}


static void __exit chrdev_exit(void)
{
	printk("drvTemplate__exit__\n");

	device_destroy(firdev.class, firdev.devnum);
	class_destroy(firdev.class);
	//unregister_chrdev(firdev.devnum, "drvTemplate");
	cdev_del(*firdev.pcdev);
	unregister_chrdev_region(firdev.devnum, 1);
}





//模块加载与卸载时会调用如下接口
module_init(chrdev_init);
module_exit(chrdev_exit);

//下面这些都是跟模块相关, 需要加上才能编译
MODULE_LICENSE("GPL");				// 模块许可证
MODULE_AUTHOR("author");			// 模块作者
MODULE_DESCRIPTION("description");	// 模块信息
MODULE_ALIAS("alias");				// 模块别名
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值