字符设备驱动的设计流程

字符设备驱动的设计流程(以蜂鸣器为列)

提示:我这里只是我所学的知识简单介绍,如果有错欢迎指正



一、定义并初始化一个字符设备

1.定义一个字符设备------>struct cdev

代码如下:

struct cdev beep_cdev;

2.定义并初始化字符设备的文件操作集

  • 文件操作集是每个设备都有,它是驱动程序对应用程序提供的一个接口

文件操作集的结构体代码如下:

  • 需要包含include <linux/fs.h>
struct inode_operations {
	struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
	void * (*follow_link) (struct dentry *, struct nameidata *);
	int (*permission) (struct inode *, int);
	struct posix_acl * (*get_acl)(struct inode *, int);
	int (*readlink) (struct dentry *, char __user *,int);
	void (*put_link) (struct dentry *, struct nameidata *, void *);
	int (*create) (struct inode *,struct dentry *,umode_t,struct nameidata *);
	int (*link) (struct dentry *,struct inode *,struct dentry *);
	int (*unlink) (struct inode *,struct dentry *);
	int (*symlink) (struct inode *,struct dentry *,const char *);
	int (*mkdir) (struct inode *,struct dentry *,umode_t);
	int (*rmdir) (struct inode *,struct dentry *);
	int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
	int (*rename) (struct inode *, struct dentry *,struct inode *, struct dentry *);
	void (*truncate) (struct inode *);
	int (*setattr) (struct dentry *, struct iattr *);
	int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
	int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
	ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
	ssize_t (*listxattr) (struct dentry *, char *, size_t);
	int (*removexattr) (struct dentry *, const char *);
	void (*truncate_range)(struct inode *, loff_t, loff_t);
	int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,u64 len);
} 

-举个列子:

int gec6818_beep_open(struct inode *inode, struct file *filp){}
	ssize_t gec6818_beep_read (struct file *filp, char __user *user_buf,size_t size, loff_t *off){}
	ssize_t gec6818_beep_write (struct file *filp, const char __user *user_buf, size_t size, loff_t *off){}
	int gec6818_beep_release (struct inode *inode, struct file *filp){}
	static const struct file_operations gec6818_beep_fops{
		.owner = THIS_MODULE,
		.open = gec6818_beep_open,
		.read = gec6818_beep_read,
		.write = gec6818_beep_write,
		.release = gec6818_beep_release
	};

3.给字符设备申请一个设备号

  • 设备号 = 主设备<<20 + 次设备号(32位)
    1.静态分配代码如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
			@from: 注册的设备号,如果一次注册多个设备号,form就是注册设备号的开始值
			@count: 次设备的数量
			@name: 设备名字,但不是设备文件的名字。 #cat /proc/devices
		返回值:
			成功返回0,失败返回错误码。

2.动态分配代码如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
			@dev:保存分配后的设备号
			@baseminor: 次设备号的开始值
			@count: 次设备号的数量
			@name: 设备名字,但不是设备文件的名字。 #cat /proc/devices
		返回值:
			成功返回0,失败返回错误码。

3.设备号注销代码如下:

void unregister_chrdev_region(dev_t from, unsigned count)
			@from: 注册的设备号
			@count: 次设备号的数量

4.初始化字符设备

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

5.将字符设备加入内核

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
		@p :定义初始化好了的字符设备
		@dev:  设备号
		@count: 次设备的数量
	返回值:
		失败返回一个负数的错误码

二、自动生成设备文件

6.创建class

创建class和device的目的是在安装驱动的时候,可以自动生成设备文件(设备节点),在卸载驱动的时候,可以自动的删除设备文件(设备节点)。

创建class和删除class代码如下:

#include <linux/device.h>
	1. 创建class
		struct class *class_create(owner, name)
			@owner: 创建的class属于哪个module,一般为 THIS_MODULE
			@name: 自定义的class的名字
		返回值:
			得到的class

	2. class的删除
		void class_destroy(struct class *cls);

7.创建device,其中device是属于class的

device是属于class的,当驱动程序有了class和device以后,内核使用mdev这个工具,根据class和device创建该驱动的设备文件。

创建device和删除device代码如下:

struct device *device_create(struct class *class, struct device *parent,
					     dev_t devt, void *drvdata, const char *fmt, ...)
			@class: device属于哪个class
			@parent: device的父设备,一般为NULL
			@devt: 设备号
			@drvdata: 驱动的data,一般为NULL
			@fmt: 设备的名字
		返回值: 
			返回创建好的device
void device_destroy(struct class *class, dev_t devt)
			@class: device属于哪个class
			@devt: 设备号

三、得到物理地址对应的虚拟地址

8.申请物理内存区,申请SFR的地址区

1. 申请物理内存区作为资源
#include <linux/ioport.h>
		struct resource *request_mem_region(start,n,name)
			@start: 物理内存区的开始地址
			@n: 物理内存的大小
			@name: 自定义的物理内存区的名字
2.释放申请的物理内存区
		release_mem_region(start, n)

## 9.内存的动态映射
#include <linux/io.h>
1.IO内存动态映射
	将一段物理地址内存区域映射成一段虚拟地址内存区
	
	void __iomem *ioremap(phys_addr_t offset, unsigned long size)
		@offset: 要映射的物理内存区开始地址
		@size: 物理内存区的大小
	返回值:
		返回映射后,虚拟地址内存区的首地址

2.解除I/O内存动态映射
	void iounmap(void __iomem * addr);

10.访问虚拟地址

1、得到相关寄存器的虚拟地址
2、 虚拟地址的类型 void __iomem *
3、 访问虚拟地址的方法(代码如下):

1) 与访问物理地址的方法一样
	*(unsigned long*)gpiocout_va &= 1<<14;

2) 使用内核提供的函数
	u32 readl(const volatile void __iomem *addr);
	void writel(u32 b,volatile void __iomem *addr);

或者
	u32 __raw_readl(const volatile void __iomem *addr);
	void __raw_writel(u32 b,volatile void __iomem *addr);
  • 举个列子:
/*10、使用虚拟地址,把beep进行初始化*/
	
	gpiocout_va    = gpioc_base_va + 0x00;
	gpiocoutenb_va = gpioc_base_va + 0x04;
	gpiocaltfn0_va = gpioc_base_va + 0x20;
	gpiocaltfn1_va = gpioc_base_va + 0x24;
	gpiocpad_va    = gpioc_base_va + 0x18;
	
	//GPIOCALTFN0 &= ~(0x03<<28);//把GPIOC14设置为GPIO功能
	writel(readl(gpiocaltfn0_va)&(~(0x03<<28)),gpiocaltfn0_va);
	//GPIOCALTFN0 |= (0x01<<28);
	writel(readl(gpiocaltfn0_va)|((0x01<<28)),gpiocaltfn0_va);
	//GPIOCOUTENB |= 1<<14;//把GPIOC14设置为输出模式
	writel(readl(gpiocoutenb_va)|((0x01<<14)),gpiocoutenb_va);
	//GPIOCOUT &= ~(1<<14);//把GPIOC14输出低电平,默认beep不响
	writel(readl(gpiocout_va)&(~(0x01<<14)),gpiocout_va);
	

总结:

本人学习尚浅,如果有大佬看了我的觉得有问题完全正常,我写的不一定都是正确的,希望大佬给我指正,谢谢!

每日一句:一件事实是一条没有性别的真理!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值