一.混杂设备
1.隶属于字符设备
2.主设备号是10
3.内核利用次设备号来区分具体的设备
4.所有的混杂设备形成一个链表
二.描述混杂设备用struct miscdevice。
主要是前三项。我们只需要初始化前三项,后面的交给内核自己处理,不用理会。
三.注册混杂设备用
同样都是遵循先分配,然后初始化,最后注册的形式。
注销混杂设备使用misc_deregister(struct miscdevice *misc);
四.注意添加必要的头文件
在内核源代码中,仿照已有的混杂设备驱动添加。
思维导图:
五.基本程序框架
六.裸机中断回顾
1.中断发生进入统一入口
2.实现注册中断处理函数
3.根据中断源编号调用中断处理程序
七.Linux系统
1.entry_armv.s文件的irq_svc标号就是统一的中断入口
2.接下来是保护现场
3.展开irq——handler宏,进一步展开arch_irq_handler_defaut(在entry-macro-multi.s),在这里获取中断编号(原理也是读取intpend等寄存器,不同平台不一样)
4.接下来是进入asm_do_IRQ(带有中端号)--》generic_handle_irq-->generic_handle_irq_desc。利用中断号找出irq_desc结构,这个结构体中有action成员,action里面有事先注册的中断处理函数。
5.取出处理函数执行
6.返回现场
八.驱动程序的任务
1.实现中断处理程序
2.注册中断处理程序到Linux内核,即中断号对应的irq_desc结构中的action成员。
3.不用的时候要注销中断驱动
九.注册使用
成功返回0,否则返回负数。一般紧接着设备的注册而注册。
1.参数说明:
2.关于flags:
(1)IRQF_DISABLED(SA_INTERRUPT):如果设置了表明是快速中断,否则是慢速中断
(2)IRQF_SHARED(SA_SHIRD)表明是共享中断号
3.关于快慢中断:
快中断就是在处理该中断的过程中不会再响应其他中断,即屏蔽掉程序状态字寄存器的IF位。
慢中断就是在响应该中断的过程中还会响应更高优先级的中断。
4.关于共享中断:
这是Linux的一个特色,多个硬件可以共享一个中断号,可以有多个中断处理函数,即action的成员可以很多个,类似于一个链表,的那个中断发生的时候,会逐一调用这些中断处理函数,在每一个处理函数中最开始就判断是不是自己对应的硬件设备发生中断,若是的话就继续执行,否则立即退出调用下一个处理函数。
5.中断处理程序:
(1)他运行在中断上下文,所以不能使用可能引起阻塞或者调度的函数。否则实时性得不到满足。
(2)一开始判断是否产生中断
(3)清除中中断标志
(4)硬件相关操作
十.注销使用
只提供中断号,对于共享中断,我们利用dev_id不一样,来进行具体的注销。
关于中断号:从entry_armv.s文件可以追踪到irqn的来历,先读取硬件中断寄存器的值得到中断序号,从而知道只记得硬件中断,但是由于要空出一部分空间给软中断,所以2440前面的16个(0-15)中断号保留给软中断,6410是32个保留。在得到的序号的基础上加上OFFSET(就是16或者有时候内核代码写作EINT0),再传递给r0,然后调用相应的中断响应函数。所以中断注册的时候要注意中断号。其实简单一点,我们只关心注册时候的中断号,这个完全可以到irqs.h文件查看,不同平台不一样,那里面全部给出了所有硬件中断对应的中断号的宏定义。
当然在硬件初始化的部分,我们也要将物理地址映射程虚拟地址才可以配置相关硬件。利用cat /proc/interrupts.查看目前已注册的中断号。你可以看到
你再到内核源代码中6410对应的irqs.h里面可以看到
#define S3C_IRQ_EINT_BASE S3C_IRQ(64+5)
#define S3C_EINT(x) ((x) + S3C_IRQ_EINT_BASE)
#define IRQ_EINT(x) S3C_EINT(x)
#define S3C_IRQ_OFFSET (32)
#define S3C_IRQ(x) ((x) + S3C_IRQ_OFFSET)
于是你发现六个按键对应的EINT0-6(实际中断号是101-106)全部被占用。而且它又不让你共享中断,即不能使用IRQF_SHARED标志参数,所以谢老师的混杂设备按键实验估计做不出来,除非换设备,即你不用按键用其他没有被内核占用的中断号对应的设备。我做的时候我记得选了一个NFC,后来加载模块以后,用dmesg命令,可以在调试信息的最后看到我之前卸载源代码的信息是成功的。
否则用按键的时候打印的信息是失败的,返回的ret是-16,表明中断号已经被占用而且不可共享。
飞凌嵌入式开发板ok6410-A,由于内核中已经编译进了6个按键中断,初步实验时,由于中断不能被申请或者共享,实验基本上不可能做了,除非自己重新编译内核。
进入cd /home/wityuan/Downloads/linux-3.0.1/arch/arm/进入cd mach-s3c64xx/
然后编辑里面的文件:找到内容:
static struct gpio_keys_button gpio_buttons[] =
{
{
.gpio = S3C64XX_GPN(0),
//.code = 25,
.code = KEY_UP,
.desc = "BUTTON1",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = S3C64XX_GPN(1),
//.code = 42,
.code = KEY_DOWN,
.desc = "BUTTON2",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = S3C64XX_GPN(2),
//.code = 50,
.code = KEY_LEFT,
.desc = "BUTTON3",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = S3C64XX_GPN(3),
//.code = 10,
.code = KEY_RIGHT,
.desc = "BUTTON4",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = S3C64XX_GPN(4),
//.code = 24,
.code = KEY_ENTER,
.desc = "BUTTON5",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = S3C64XX_GPN(5),
//.code = 38,
.code = KEY_ESC,
.desc = "BUTTON6",
.active_low = 1,
.wakeup = 0,
}
};
将内部的赋值全部注释掉,然后重新编译内核,即可。
重启系统之后,会发现按键作用到的开发板,没有了上下左右键,退出与进入的功能。从而屏蔽了系统中原来使用的中断号。重新烧写内核即可。然后测试文件如下
十一.中断嵌套
1.当一个中断处理的时候,发生另一个中断。
2.如果是慢速中断:
(1)同类型的中断会被忽略
(2)不同类型的会被响应
3.如果是快速中断:不管是否是同类型,都会被忽略
4.基于以上分析,为了避免中断丢失,我们要尽可能缩短中断处理的时间,从而引出了中断分层的概念。
十二.中断分层
1.将中断分为上下两部分,上部分是必须放在中断处理函数的,对硬件相关的操作。下部分是对终端的判断以及检测盒后续的清理工作,交给内核处理。
2.中断分层有三种方式,分别为tasklet,软中断以及工作队列的方式。我们最常用的是工作队列的方式。
十三.分层的具体过程
1.下半部分程序按照一定的格式形成内核能够识别的工作
2.将这个工作加入到CPU核上的工作队列(链表),以为内每一个CPU核都会创建一个工作队列
3.内核为每一个工作队列创建一个内核线程
4.在CPU空闲的时候调用该线程,从而执行相应的中断程序下部分。
5.执行完毕之后立即从队列中将该工作删除
十四.
1.执行某项工作实质上就是执行某一个函数,至于具体到哪一个函数就是看工作项是如何定义function成员的
2.工作队列的使用
(1)用workqueue_struct描述一个工作队列
struct workqueue_struct {
unsigned int flags; /* I: WQ_* flags */
union {
struct cpu_workqueue_struct __percpu *pcpu;
struct cpu_workqueue_struct *single;
unsigned long v;
} cpu_wq; /* I: cwq\'s */
struct list_head list; /* W: list of all workqueues */
struct mutex flush_mutex; /* protects wq flushing */
int work_color; /* F: current work color */
int flush_color; /* F: current flush color */
atomic_t nr_cwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* F: first flusher */
struct list_head flusher_queue; /* F: flush waiters */
struct list_head flusher_overflow; /* F: flush overflow list */
mayday_mask_t mayday_mask; /* cpus requesting rescue */
struct worker *rescuer; /* I: rescue worker */
int saved_max_active; /* W: saved cwq max_active */
const char *name; /* I: workqueue name */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
(2)用work_struct描述一个工作项,相当于节点(队列成员)
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
(3)最关键爱的就是func成员
typedef void (*work_func_t)(struct work_struct *work);
他是下半部分实现的关键。
(4)工作队列实现中断分层三部曲
(5)要想用中断分层必须加上MODULE_LICENSE(“GPL”);宏定义
(6)并非一挂载就立即执行工作,而是由内核线程自动判断何时执行工作。
十五.实际使用中,不一定要用户自己手动创建工作队列,Linux内核已经自动创建了一个默认的一个工作队列keventd_wq,用户只需要创建一个工作以及提交给默认的工作队列(schedule_work(my_func))。通常在中断处理函数的结尾提交工作。
十六.按键去抖
1.不可避免的机械抖动
2.消除抖动一般分为软硬两个方向
3.软件又可分为循环空操作和使用定时器
十七.Linux定时器
1.描述方式(定义变量的结构体)
2.使用步骤
(1)定义定时器变量
(2)初始化定时器,一般放在模块初始化函数做。
-->用init_timer完成大部分初始化
-->设置超时函数
(3)注册定时器add_timer,一般也放在模块初始化函数做。
(4)启动定时器mod_timer,在中断处理程序中启动(按键按下的时候启动),而且放在下半部分来做。有两个参数第一个参数表示你要启动的定时器,第二个参数中的jiffies表示当前时间,后面加一个你要延时的时间,1HZ表示1秒。
(5)定时器不会重复定时,每一次定时时间到达就失效。然后下次启动的话还需要重新mod_timer
3.
(1)也要注意想噶unyingjaindeGPIO配置
(2)区里地知道虚拟地址的映射
(3)读写硬件的函数readw,writew等
(4)对按键按下状态的判断。
十八.多按键的支持
1.一个设备可以对应多个中断号,故可以注册多个中断号
2.不同按键中断号不一样,注册中断号的时候依次注册即可(模块初始化中进行)
3.还有按键增多以后,相应的硬件GPIO的控制也要做变化(conf寄存器)
4.中断处理程序中,或者在超时函数中要对按下的按键进行判断(data寄存器)
十九.应用程序访问按键编号
1.在驱动程序里加入read函数,并且加到fileoperation结构体里面,方便应用程序调用
2.read函数要用到copy_to_user,使得能够将按键编号返回到用户空间(应用程序)
3.应用程序的步骤无非就是
(1)打开设备
(2)读取设备,并将读取结果存放到指定变量并对结果判断从而进行相应处理
(3)关闭设备
4.加载内核模块(注册底层驱动)的时候后,混杂设备的主设备号永远是10,且属于字符设备
5.mknod创建设备节点(设备文件)的时候,注意和应用程序的open函数相对应,次设备号和底层驱动相对应。
二十.阻塞驱动模型
1.不是每一个进程对设备的访问都能随时得到满足。
2.当进程对设备进行读写操作时,如果设备还没有准备好,就应该有一种机制让进程等待,知道设备准备好让他进行读写操作。类似于等公交车。
3.为了实现这种机制,就有了阻塞模型。
(1)访问设备
(2)不满足访问条件,然后让进程进入“候车室”内核等待队列
(3)当访问条件满足的时候,唤醒候车室中的等待进程
4.内核等待队列的相关函数
5.定义一个等待队列一般放在全局变量
6.初始化一般是在驱动加载函数
7.加入等待队列则是在read或者write函数中看条件是否满足。
8.读走数据之后,要把按键值清空。也是在read或者write中。
9.唤醒等待进程在超时函数中。即中断下半部分