字符设备驱动 (一)

1 开发环境

  • Linux Kernel 4.18.0
  • QEMU 5.2.0-vexpress
  • Source Insight 3.5

2 字符设备驱动

  • Linux从各异的设备中提取共性,将其划分成三大类:字符设备、块设备和网络设备。
  • 常见的字符设备有键盘、鼠标、液晶显示、打印机等。

2.1 字符设备结构体

  • 在Linux内核中使用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;
  • file_operations 操作集合,定义了字符设备驱动提供给虚拟文件系统的接口函数
struct file_operations {
	...
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	...
} __randomize_layout;
  • 设备号操作宏
#define MAJOR(dev)	((dev)>>8)
#define MINOR(dev)	((dev) & 0xff)
#define MKDEV(ma,mi)	((ma)<<8 | (mi))

2.2 字符设备操作集合

  • 动态分配cdev
struct cdev *cdev_alloc(void);
  • 初始化设备号 dev
//已经知道设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//像系统申请未使用的设备号(常用), Allocates a range of char device numbers.
//The major number will be chosen dynamically, and returned (along with the first minor number)in @dev. 
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
  • 初始化cdev成员ops, kobj, list
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
  • 注册设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
  • 释放设备号
//This function will unregister a range of @count device numbers, starting with @from.*/
void unregister_chrdev_region(dev_t from, unsigned count);
  • 释放设备
void cdev_del(struct cdev *p);

3 设备驱动

  • 简单实现设备驱动基本操作
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define 	ERIC_SIZE		(0x1000)
#define 	ERIC_MAJOR		(230)
#define 	DEVICE_NUM		(10)

#define 	ERIC_MAGIC		('g')
#define 	MEM_CLEAR		_IO(ERIC_MAGIC, 0)

static int eric_major	= ERIC_MAJOR;
module_param(eric_major, int, S_IRUGO);


struct eric_dev {
	struct cdev cdev;
	unsigned char mem[ERIC_SIZE];
};

struct eric_dev* eric_devp;

/*
#define _IOC_NRSHIFT	0
#define _IOC_TYPESHIFT	(_IOC_NRSHIFT+_IOC_NRBITS)		//8
#define _IOC_SIZESHIFT	(_IOC_TYPESHIFT+_IOC_TYPEBITS)	//16
#define _IOC_DIRSHIFT	(_IOC_SIZESHIFT+_IOC_SIZEBITS)	//30

#define _IOC(dir,type,nr,size) \
	(((dir)  << _IOC_DIRSHIFT) | \
	 ((type) << _IOC_TYPESHIFT) | \
	 ((nr)   << _IOC_NRSHIFT) | \
	 ((size) << _IOC_SIZESHIFT))
*/
//|**dir**|**type**|**nr**|**size**|

//|**dir**|**size**|**type**|**nr**|
//|---2---|---14---|----8---|---8--|
long eric_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct eric_dev* dev = filp->private_data;
	
	switch(cmd){
	case MEM_CLEAR:
		memset(dev->mem, 0, ERIC_SIZE);
		printk(KERN_INFO "mem is set to zero\n");
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

/*
The _llseek() system call repositions the offset of the open file description associated with the file descriptor fd to (offset_high<<32) | offset_low bytes relative to the beginning of the file, 
the current file offset, or the end of the file, depending on whether whence is , respectively.  It returns the resulting file position in the argument result.
*/
loff_t eric_llseek (struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	
	switch(orig){
	//从文件头开始
	case SEEK_SET:
		if((offset<0) || ((offset > ERIC_SIZE)))
			ret = -EINVAL;
		filp->f_pos = (unsigned int) offset;
		ret = filp->f_pos;
		break;
	//从当前位置开始
	case SEEK_CUR:
		if(((filp->f_pos+offset)<0) || ((filp->f_pos + offset) > ERIC_SIZE)){
			ret = -EINVAL;
		}
		filp->f_pos += (unsigned int) offset;
		ret = filp->f_pos;
		break;
	//从文件尾开始
	case SEEK_END:
		filp->f_pos = (unsigned int) (ERIC_SIZE+ offset);		
		ret = filp->f_pos;
		if(ret > ERIC_SIZE)
			ret = ERIC_SIZE;
		break;
	default:
		ret = -EINVAL;
		break;
	}
	
	printk(KERN_INFO "eric_llseek\n");
	return ret;
}

ssize_t eric_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	
	struct eric_dev* dev = filp->private_data;
	
	if(p >= ERIC_SIZE)
		return 0;
	
	if(count > ERIC_SIZE - p)
		count = ERIC_SIZE - p;
	
	if(copy_to_user(buf, dev->mem + p, count))
		ret = -EFAULT;
	else{
		*ppos += count;
		ret = count;
		printk(KERN_INFO "eric_read\n");
	}
	return ret;
}
ssize_t eric_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	
	struct eric_dev* dev = filp->private_data;
	
	if(p >= ERIC_SIZE)
		return 0;
	
	if(count > ERIC_SIZE - p)
		count = ERIC_SIZE - p;
	
	if(copy_from_user(dev->mem + p, buf, count))
		ret = -EFAULT;
	else{
		*ppos += count;
		ret = count;
		printk(KERN_INFO "eric_write\n");
	}
	return ret;
}
int eric_open(struct inode *inode, struct file *filp)
{
	struct eric_dev* dev = container_of(inode->i_cdev, struct eric_dev, cdev);

	filp->private_data = dev;
	
	printk(KERN_INFO "eric_open\n");
	return 0;
}
int eric_release(struct inode *inode, struct file *filp)
{
	printk(KERN_INFO "eric_release\n");
	return 0;
}

const struct file_operations eric_file_opers = {
	.owner = THIS_MODULE,
	.llseek = eric_llseek,
	.unlocked_ioctl = eric_ioctl,
	.read = eric_read,
	.write = eric_write,
	.open = eric_open,
	.release = eric_release
};

static void eric_setup_cdev(struct eric_dev* dev, int index)
{
	int err = 0;
	int devnum = MKDEV(ERIC_MAJOR, index);
	cdev_init(&(dev->cdev), &eric_file_opers);
	
	dev->cdev.owner = THIS_MODULE;
	
	err = cdev_add(&(dev->cdev), devnum, 1);
	
	if(err)
		printk(KERN_INFO "error %d adding eric_dev%d", err, index);
	
}
static int __init eric_char_init(void)
{
	dev_t  devnum = MKDEV(eric_major, 0);
	
	int ret=0, i = 0;
	printk("eric_char_init start\n");
	
	if(eric_major)
		ret = register_chrdev_region(devnum, DEVICE_NUM, "eric_char");
	else{
		ret = alloc_chrdev_region(&devnum, 0, DEVICE_NUM, "eric_char");
		eric_major = MAJOR(devnum);
	}
	if(ret < 0)
		return ret;
	
	eric_devp = kzalloc(sizeof(struct eric_dev)*DEVICE_NUM, GFP_KERNEL);
	
	if(eric_devp == NULL){
		ret = -ENOMEM;
		goto fail_malloc;
	}
	
	for(i = 0; i < DEVICE_NUM; i++)
		eric_setup_cdev(eric_devp+i, i);
	
	printk(KERN_INFO "eric_char_init end\n");
	return ret;
	
fail_malloc:
	printk(KERN_INFO "eric_char_init_fail_malloc\n");
	unregister_chrdev_region(devnum, DEVICE_NUM);
	return ret;
    
}

module_init(eric_char_init);

static void __exit eric_char_exit(void)
{
	dev_t  devnum = MKDEV(eric_major, 0);
	int i;
	for (i = 0; i < DEVICE_NUM; i++)
		cdev_del(&(eric_devp + i)->cdev);
	
	if(eric_devp)
		kfree(eric_devp);
	
	if(eric_major)
		unregister_chrdev_region(devnum, DEVICE_NUM);

	printk(KERN_INFO "eric_char_exit\n");
}

module_exit(eric_char_exit);
MODULE_AUTHOR("eric dong");
MODULE_LICENSE("GPL v2");

4 QEMU 对应结果

在这里插入图片描述

5 参考书籍

  • Linux 设备驱动开发详解-基于最新的 Linux 4.0内核(宋宝华编著)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值