字符设备基础驱动开发

1、字符设备基础知识

该图片来源百度
由图所示,Linux系统中有为三类设备:字符设备、块设备、网络接口设备。
字符设备: 字符设备是指在I/O传输过程中以字符为单位传输的设备,例如键盘、打印机。
块设备: 块设备是I/O设备中的一类,是将信息存储在固定大小的块中,每个块都有自己的地址,还可以在设备的任意位置读取一定长度的数据,例如硬盘,U盘,SD卡等。
在Linux系统中,“一切皆文件”。所以在Linux系统将字符设备、块设备当作文件来处理,以访问文件的方式来处理。

2、字符设备驱动开发

2.1 字符设备驱动要素

1、必须有一个设备号,用与区分众多设备;
2、用户必须知道设备驱动对应的设备节点(设备文件)
3、实现对设备操作的文件接口,例如open、read、write、close等文件接口。

2.1.1 申请主设备号与注销主设备号

int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)

  • 参数1:主设备号
    设备号(32bit–dev_t)==主设备号(12bit) + 次设备号(20bit)
    主设备号:表示一类设备–camera
    次设备号: 表示一类设备中某一个:前置,后置
    【给定到方式有两种:】
    1)动态–参数1直接填0
    2)静态–指定一个整数,250
  • 参数2: 描述一个设备信息,可以自定义
    /proc/devices列举出所有到已经注册的设备
  • 参数3: 文件操作对象–提供open, read,write
  • 返回值: 正确返回0,错误返回负数

void unregister_chrdev(unsigned int major, const char * name)
参数1:主设备号
参数2: 描述一个设备信息,可以自定义

2.1.2 创建设备节点

手动创建

指令:mknod /dev/设备名 类型 主设备号 次设备号
例如:mknod /dev/chr0 c 250 0
缺点:/dev/目录中的文件都是在内存中的,断电后/dev/文件就会消失

自动创建

(自动创建设备节点通过udev/mdev机制)
使用函数如下:
1、创建一个类
struct class* class_create(owner, name);

  • 参数一:THIS_MODULE
  • 参数二:字符串名字,自定义
  • 返回值:成功返回一个class指针

2、创建一个设备文件
struct device* device_create(struct class* class , struct device * parent ,dev_t devt, void * drvdata , const char* fmt ,…);

  • 参数一:class结构体,class_create调用后的返回值
  • 参数二:表示父类,一般填NULL
  • 参数三:设备号类型(次设备号)
    #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
    #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
  • 参数四:私有数据,一般直接填NULL
  • 参数五:表示可变参数,字符串,表示设备节点名字

销毁动作
void device_destroy(devcls, MKDEV(dev_major, 0));
参数1: class结构体,class_create调用之后到返回值
参数2: 设备号类型 dev_t

void class_destroy(devcls);
参数1: class结构体,class_create调用之后到返回值

2.1.3 在驱动中实现文件操作的I/O接口

在驱动中实现文件I/O接口,使得应用程序可以调用文件I/O驱动该设备。
1)驱动中实现文件io操作接口:struct file_operations

		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);
		}; //函数指针的集合,其实就是接口,我们写驱动到时候需要去实现

使用方法

const struct file_operations my_fops = {
				.open = chr_drv_open,
				.read = chr_drv_read,
				.write = chr_drv_write,
				.release = chr_drv_close,
		};

2)应用程序如何去调用文件io去控制驱动–open,read,…

fd = open("/dev/chr2", O_RDWR);
			if(fd < 0)
			{
				perror("open");
				exit(1);
			}
read(fd, &value, 4);
write(fd, &value, 4);
close(fd);

2.1.4 应用程序和驱动的数据交互

主要依靠两个函数:
1) int copy_to_user(void _user * to ,const void from,unsigned long n)

  • 将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()使用
  • 参数一:用户空间的一个Buffer
  • 参数二:内核空间的一个Buffer
  • 参数三:个数
  • 返回值:大于0表示出错,剩下多少个没有拷贝成功,等于0,表示正确

2)int copy_from_user(void * to, const void __user * from, unsigned long n)

  • 将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用
  • 参数一:内核驱动中的一个buffer
  • 参数二:应用空间到一个buffer
  • 参数三:个数
  • 返回值:大于0表示出错,剩下多少个没有拷贝成功,等于0,表示正确

2.1.5 控制外设

控制外设,其实就是控制地址,内核驱动中是通过虚拟地址操作,这里需要用到映射地址函数。
void * ioremap(cookie , size)

  • 参数一:物理地址
  • 参数二:长度
  • 返回值:虚拟地址

去映射——解除映射
void * iounmap(void _iomem * addr)

  • 参数:映射后的虚拟地址;

2.1.6 操作寄存器地址的方法

1)volatile unsigned long *gpxcon;
*gpxcon &=~(0xf<<28);

2)readl()/writel()函数
u32 readl(const volatile void __iomem addr)
从地址中读取地址空间的值
void writel(unsigned long value , const volatile void __iomem add)

将value的值写入到addr地址
例子:
// gpio的输出功能的配置
u32 value = readl(led_dev->reg_virt_base);
value &= ~(0xf<<28);
value |= (0x1<<28)
writel(value, led_dev->reg_virt_bas);

或者替换成:
writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );

2.2 编写字符设备驱动的步骤和规范

步骤
1)实现模块加载和模块卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
2)在模块加载入口函数中实现以下功能:

  • 申请设备号 (内核中用于区分和管理不同字符设备)
    register_chrdev(dev_major, “chr_dev_test”, &my_fops);

  • 创建设备节点(为用户提供一个可操作到文件接口–open())
    struct class *class_create(THIS_MODULE, “chr_cls”);
    struct device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, “chr2”)

  • 硬件初始化
    a.地址的映射
    b.中断的申请
    c.实现硬件的寄存器的初始化

  • 实现file_operations

规范

  • 面向对象编程思想
    用一个结构体来表示一个对象,设计一个类型,描述一个设备的信息。
struct led_desc{
				unsigned int dev_major; //设备号
				struct class *cls;
				struct device *dev; //创建设备文件
				void *reg_virt_base;
			};
struct led_desc *led_dev;//表示一个全局的设备对象
实例化全局的设备对象——分配空间
GFP_KENEL 如果当前内存不够用的时候,该函数会一直阻塞
led_dev = Kmalloc(sizeof(struct led_desc),GFP_KERNEL);
if(led_dev == NULL)
{
	printk(KERN_ERR"malloc error\n");
	return -ENOMEN;
}
			
  • 出错处理
    在某一个位置出错了,要将之前申请的资源进行释放
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);	
		
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
	printk(KERN_ERR "register_chrdev error\n");
	ret = -ENODEV;
	goto err_0;
}
err_0:
	kfree(led_dev);
	return ret;

2.3 Led的驱动代码

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

#define GPX2_CON 0x11000C40
#define SIZE 8

struct led_desc {
	unsigned int major;
	struct class * cls;
	struct device * dev;
	void * reg_virt_base;
	
};

ssize_t my_led_read (struct file *fp, char __user *buf, size_t n, loff_t *loft)
{
	printk("-----------%s-----------\n",__FUNCTION__);
	
	return 0;

}
ssize_t my_led_write (struct file *fp, const char __user *buf, size_t n, loff_t *loft)
{
	int ret ,value;
	ret = copy_from_user(&value,buf,n);
	if(ret>0)
	{
		printk("copy_from_user err\n");
		return -EFAULT;
	}
	if(value==1)
		{
			writel(readl(led_dev->reg_virt_base+4) | (0x1<<7), led_dev->reg_virt_base+4);
		}
	else
		{
			writel(readl(led_dev->reg_virt_base+4) & ~(0x1<<7), led_dev->reg_virt_base+4);
		}
	return 0;
}
int my_led_close (struct inode *inode, struct file *fp)
{
	printk("-----------%s-----------\n",__FUNCTION__);
	
	return 0;

}
int my_led_open (struct inode *inode, struct file *fp)
{
	printk("-----------%s-----------\n",__FUNCTION__);
	
	return 0;

}

const struct file_operations  my_fops={
.open = my_led_open,
.read = my_led_read,
.write = my_led_write,
.release = my_led_close,
};
struct led_desc *my_led;
static int __init test_chr_init(void)
{
	int ret;
	//给对象分配空间
	my_led = kmalloc(sizeof(struct led_desc), GFP_KERNEL); 
	if(my_led==NULL)
		{
			printk(KERN_ERR "malloc err\n");
			return -ENOMEM;
		}

	//申请设备号
	my_led->major = register_chrdev(0, "led_f", &my_fops);
	if(my_led->major<0)
		{
			printk(KERN_ERR "register_chrdev err\n");
			ret = -ENODEV;
			goto err_0;
		} 

	//创建设备节点
	my_led->cls = class_create(THIS_MODULE, "chr_led");
	if(IS_ERR(my_led->cls))
		{
			printk(KERN_ERR"class_create err\n");
			ret = PTR_ERR(my_led->cls);
			goto err_1;
		}
	
	my_led->dev = device_create(my_led->cls, NULL, MKDEV(my_led->major, 0), NULL, "led_yf");
	if(IS_ERR(my_led->dev))
		{
			printk(KERN_ERR"device_create err\n");
			ret =PTR_ERR(my_led->dev);
			geto err_2;
		}
	//映射地址
	my_led->reg_virt_base = ioremap(GPX2_CON, SIZE);
	if(my_led->reg_virt_base == NULL)
		{
			printk(KERN_ERR "ioremap err\n");
			ret = -ENOMEN;
			goto err_3;
		}
	//初始化地址
	writel((readl(my_led->reg_virt_base) & (~(0xf<<28))) | (0x1<<28),my_led->reg_virt_base);
	return 0;
	
	err_3:
	device_destroy(my_led->cls,MKDEV(my_led->major, 0));
	err_2:
	class_destroy(my_led->cls);
	err_1:
	unregister_chrdev(my_led->major, "led_f");
	err_0:
	kfree(my_led);
	return ret;
}
static void __exit test_chr_exit(void)
{
	iounmap(my_led->reg_virt_base);
	device_destroy(my_led->cls,MKDEV(my_led->major, 0));
	class_destroy(my_led->cls);	
	unregister_chrdev(my_led->major, "led_f");
	kfree(my_led);

}
module_init(test_chr_init);
module_exit(test_chr_exit);
MODULE_LICENSE("GPL");




























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值