字符设备驱动的设计流程(以蜂鸣器为列)
提示:我这里只是我所学的知识简单介绍,如果有错欢迎指正
文章目录
一、定义并初始化一个字符设备
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);
总结:
本人学习尚浅,如果有大佬看了我的觉得有问题完全正常,我写的不一定都是正确的,希望大佬给我指正,谢谢!
每日一句:一件事实是一条没有性别的真理!