应用实现策略,驱动实现机制
驱动框架
驱动框架 (字符设备 / 块设备 / 网络设备)
#include <linux/init.h>
#include <linux/module.h>
加载 {
1.申请设备编号 (动静与主副)
2.创建设备节点 (自动与手动)
3.硬件的初始化 (映射与中断)
4.硬件接口函数 (实现f o p s)
error :
}
卸载{
释放空间
}
认证声明
模块加载函数:安装模块时被系统自动调用的函数,通过module_init宏来指定。
模块卸载函数:卸载模块时被系统自动调用的函数,通过module_exit宏来指定。
模块可选信息
许可证申明:MODULE_LICENSE("GPL");
用来告知内核该模块带有一个许可证。有效的许可证有“GPL”,“GPLv2”等。
作者申明:MODULE_AUTHOR(“LuckY”);
模块描述:MODULE_DESCRIPTION(“hello world module”);
模块版本:MODULE_VERSION(“V1.0”);
模块别名:MODULE_ALIAS(“simple module”);
常用函数
①:申请设备编号
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是:
1. static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) 动静申请主设备号
//参数1 ---- major不为0,表示要申请的主设备号,该设备号由我们自己指定,这种叫静态申请;
major为0,这时register_chrdev会返回主设备号,由系统自动分配,这种叫动态申请
//参数2 ---- 字符串,表示驱动的描述信息,自定义
//参数3 ---- 文件操作对象的指针
//返回值:major不为0,成功---返回0,失败---返回错误码; major为0,成功----主设备号,失败--返回错误码
2. 静: int register_chrdev_region(dev_t from, unsigned count, const char *name)
//参数1 ---- dev_t 静态指定的设备号
//参数2 ---- 设备的个数
//参数3 ---- 字符串,描述驱动的名称,自定义
//返回值-----成功:0,失败---错误码
动: int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
//参数1 ---- 存放设备号的变量的地址
//参数2 ---- 次设备号
//参数3 ---- 设备的个数
//参数4 ---- 字符串,描述驱动的信息,自定义
//返回值-----成功:0,失败---错误码
struct cdev {
struct kobject kobj; struct module *owner;//填充时,值要为 THIS_MODULE,表示模块 const struct file_operations *ops;//这个file_operations结构体,注册驱动的关键,要填充成这个结构体变量 struct list_head list; dev_t dev;//设备号,主设备号+次设备号 unsigned int count;//次设备号个数 };
struct cdev *cdev_alloc(void) // 申请cdev的空间
void cdev_init(struct cdev *cdev, const struct file_operations *fops) // 初始化cdev的成员
int cdev_add(struct cdev *p, dev_t dev, unsigned count) // 将cdev加入到内核中----链表(只有注册到内核,内核才能统一管理)
cdev_del // 从内核中注销掉一个驱动注销驱动
②:创建设备节点
1. 手动创建:mknod 设备节点名称 类型 主设备号 次设备号 (mknod /dev/hellow c 254 0 )
2. 自动创建:
struct class * class_create(struct module *owner, const char *name)
//参数1 ---- 当前模块 THIS_MODULE用这个宏代替
//参数2 ---- 字符串,类的描述信息,自定义
//返回值:成功-----struct class结构体的地址,失败----NULL
struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...)
//参数1 ---- struct class * 类型的指针
//参数2 ---- 父类,一般为NULL
//参数3 ---- 设备号:dev_t
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
//参数4 ---- 私有数据,一般为NULL
//参数5 ---- 设备文件的名称
//变参----与参数5一起使用,用来定义设备文件的名称
//返回值: 成功---struct device 结构体的地址,失败-----NULL
③:硬件的初始化
1. 地址映射:
gpco_conf = ioremap(0x1F02C04,8);
gpco_data = ioremap(0x1F02C10,8);
*gpco_conf &= ~(0x07<<8); //8--10清0
*gpco_conf |= 0x01<<8; //8---10赋值:00010001
代码示例:https://www.cnblogs.com/panda-w/p/10966500.html
2. 中断申请:
(1).申请
unsigned int irqno = IRQ_EINT(1);
static inlineint gpio_to_irq(unsigned gpio)
返回中断编号传给request_irq()和free_irq()
static inline int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
//参数1 ----- 中断号
//参数2 ----- 中断处理函数:irq_handler_t 等价 typedef irqreturn_t (*irq_handler_t)(int, void *)
//参数3 ----- 中断触发方式:
#define IRQF_TRIGGER_NONE 0x00000000 //内部中断触发
#define IRQF_TRIGGER_RISING 0x00000001 //上升沿触发
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发
#define IRQF_TRIGGER_HIGH 0x00000004 //高电平触发
#define IRQF_TRIGGER_LOW 0x00000008 //低电平触发
//参数4 ----- 字符串,描述信息,自定义
//参数5 ----- 传给中断处理函数的参数
//返回值 ----- 成功:0,失败:错误码
(2).处理
irqreturn_t xxx_irq_svc(int irqno, void *dev){
........
return IRQ_HANDLED;
}
(3).释放
void free_irq(unsigned int irq, void *dev_id)
//参数1 ----- 中断号
//参数2 ----- 必须与request_irq的最后一个参数保持一致
④:硬件接口函数
struct file_operations {
① struct module *owner;
② int (*open) (struct inode *, struct file *);
③ int (*release) (struct inode *, struct file *);
④ ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
⑤ ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
⑥ loff_t (*llseek) (struct file *, loff_t, int);
⑦ long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
⑧ int (*mmap) (struct file *, struct vm_area_struct *);
⑨ unsigned int (*poll) (struct file *, struct poll_table_struct *);
⑩ int (*flock) (struct file *, int, struct file_lock *);
⑪ int (*flush) (struct file *, fl_owner_t id);
};
int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg);
通过发送命令的方式,来控制设备
重要结构体
常用机制
①:中断申请
代码示例:https://www.cnblogs.com/panda-w/p/10991402.html
中断下半部
代码示例:https://www.cnblogs.com/panda-w/p/10991450.html
③:poll多路复用 和 轮询
应用空间:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
----------------------------------------------------
内核驱动:
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
//参数1-----file结构体指针
//参数2-----等待队列头
//参数3-----与等待队列相关联的表
代码示例:https://www.cnblogs.com/panda-w/p/10991424.html
②:mmap应用
应用空间:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
//参数1 ----- 指定映射之后的虚拟空间的的位置,一般为NULL,表示由系统自动分配映射的虚拟空间
//参数2 ----- 映射的空间长度
//参数3 ----- 对内存的操作权限:PROT_EXEC PROT_READ PROT_WRITE PROT_NONE
//参数4 ----- 是否允许其他进程映射这块内存:MAP_SHARED MAP_PRIVATE
//参数5 ----- 打开的文件的描述符
//参数6 ----- 从(物理)内存的偏移多少字节的位置开始映射
//返回值:成功----映射后的虚拟空间的起始地址,失败----NULL
------------------------------------------------------------------------------------------
内核驱动:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot)
//参数1 ---- 表示映射空间的相关信息
//参数2 ---- 映射到应用空间的起始地址
//参数3 ---- 被映射的物理内存的页地址
//参数4 ---- 映射的空间大小
//参数5 ---- 映射的空间的权限
代码示例:https://www.cnblogs.com/panda-w/p/10991438.html
⑤:非/阻塞IO
1,阻塞IO的实现:(在应用空间中,有很多函数默认就是阻塞IO:scanf(), accept(),connect(),recv(),read()等 )
1》 需要初始化一个等待队列头 ------------ 驱动的加载函数中,中断申请之后调用
init_waitqueue_head(wait_queue_head_t *q)
2》根据条件决定是否让进程入休眠状态 ---------- 在xxx_read()中调用
wait_event_interruptible(wait_queue_head_t wq,int condition)
//参数1 ---- 等待队列头
//参数2 ---- 一个条件变量: 0-----休眠,1-----不休眠
3》当资源可用时,必须唤醒阻塞的进程 ----------------- 在中断处理函数中唤醒进程
wake_up_interruptible(wait_queue_head_t * x)
代码示例:https://www.cnblogs.com/panda-w/p/10991359.html
2,非阻塞IO的实现:
应用程序中:
fd = open("/dev/button",O_RDWR|O_NONBLOCK);
read() ------- 有数据,则读出数据,没有数据,则直接返回,返回一个错误码-EAGAIN
-------------------------------------------------------------------------
内核驱动中:
ssize_t xxx_read(struct file *filp , char __user *buf , size_t size, loff_t *flags)
{
int ret;
printk("--------^_^ %s------------
",__FUNCTION__);
//判读open时,有没有设置flags为NONBLOCK
if(filp->f_flags & O_NONBLOCK && !button_dev->have_data)
return -EAGAIN;
...........
}
代码示例:https://www.cnblogs.com/panda-w/p/10991411.html
⑥:数据传递
1. static inline long copy_to_user(void __user *to,const void *from, unsigned long n)
//内核空间传递数据给应用空间 ---------- 实现read接口
//参数1 ----- 用户空间的地址
//参数2 ----- 内核空间数据的地址
//参数3 ----- 传递的数据的长度
//返回值------ 成功:0,失败:未传递的数据的字节数
2. static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
//应用空间传递数据给内核空间 ---------- 实现wirte接口
//参数1 ----- 内核的空间地址
//参数2 ----- 用户空间数据的地址
//参数3 ----- 传递的数据的长度
//返回值------ 成功:0,失败:未传递的数据的字节数
代码示例:https://www.cnblogs.com/panda-w/p/10991322.html
⑦:GPIO函数
7,将某个gpio口配置为特定功能
int s3c_gpio_cfgpin(unsigned int pin, unsigned int config)
8,将某个gpio口内部上拉或者下拉
int s3c_gpio_setpull(unsigned int pin, s3c_gpio_pull_t pull)
⑧:ioctl应用
通过发送命令的方式,来控制设备
long xxx_ioctl(struct file *filp, unsigned int cmd , unsigned long args)
代码示例:https://www.cnblogs.com/panda-w/p/10991312.html
<笔记>
1. 在linux中,设备号用32位的整数表示:
分两部分:
主设备号: 表示一类设备 ------ 高12位
次设备号: 表示某一类设备中的某个具体设备----低20位
2. 查看设备编号:cat /proc/devices
3. 查看设备节点:ls /dev
4. 常用头文件 :
#include <linux/×××.h> 平台无关
#include <asm/×××.h> CPU体系结构
#include <plat/×××.h> IC公司相关
#include <mach/×××.h> 开发板相关
5. 驱动查看位置:/sys/module
6. 主次设备号表示:不同类别设备与同一类别的不同设备
7. PCI :外围器件互联
8. ioctl:是应用层的API
9. boot 0x****** 从开始运行
10. 驱动设备的寄存器,完成设备的轮询、中断处理、DMA通信,最终让通信设备可以收发数据
11. cat /proc/devices lsmod 查看设备信息
12. 驱动是异步机制
13.