本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册、卸载、操作函数集。2.中断的申请及释放。3.等待队列的使用。4.工作队列的使用。5.定时器的使用。6.向linux内核中添加外部中断驱动模块。7.完整驱动程序代码。linux的内核版本为linux2.6.32.2。

一、linux模块的框架以及混杂设备相关知识

1.内核模块的框架如下图所示,其中module_init()(图中有误,不是modules_init)只有在使用insmod命令手动加载模块时才会被调用,将模块静态编译到内核时该函数不会运行,但是会将入口调用函数的地址传递到内核中,启动内核时入口函数将会被执行。(出口调用函数同理)。

外部中断的驱动使用混杂设备的模型,混杂设备是一种特殊的字符设备,其主设备号固定为“10”。

描述混杂设备的结构体定义为:

struct miscdevice

{

int minor;   /*次设备号*/                     (加粗需初始化)

const char *name;   /*设备名*/

const struct file_operations *fops;   /*文件操作*/

struct list_head list;

struct device *parent;

struct device *this_device;

}

其中,*fops指向描述该设备的操作函数集,包括open(),read(),close()等函数。用户在应用程序中调用open()等操作将会连接到内核模块中定义的操作函数。

混杂设备的注册使用函数:

misc_register(struct miscdevice *misc);

混杂设备的注销使用函数:

misc_deregister(struct miscdevice *misc);

二、中断的申请与释放

中断注册函数的原型是:

int request_irq(unsigned int irq, irqreturn_t (handler*)(int irq, void *dev_id ), unsigned long flags, const char *devname, void *dev_id);

其中,irq为所申请的中断,如IRQ_EINT0,该宏在irqs.h文件中定义。第二个参数为中断处理函数名。第三个参数为该中断的一些属性参数,如IRQ_TYPE_EDGE_BOTH(表示该外部中断由双边沿触发)、IRQF_DISABLED(SA_INTERRUPT)(快速中断)、IRQF_SHARED(SA_SHIQR)(共享中断)等。*devname为设备文件名(一般同混杂设备名相同)。*dev_id为共享中断id(中断触发时,具有相同共享id的中断均进入中断响应,注意格式)。

中断注销函数的原型是:

void free_irq(unsigned int irq, void *dev_id);

参数意义同上。

在中断处理函数之中不能使用可能引起阻塞或者调度的操作,如kmalloc、ioremap等函数,否则有可能引起内核崩溃。

另外,为了使中断处理函数尽可能地短小快速,会将中断处理分为上下两个部分。上半部用于处理比较紧急的部分,如寄存器相关的操作等,下半部用于处理不那么紧急的操作,下半部的操作一般在上半部返回之前提交内核,工作队列是一种常用于实现下半部操作的方法。在上半部完成之后,中断函数直接返回以便继续响应外部中断的触发。

三、等待队列的使用

为了提高处理器效率,避免处理器的轮询操作,当条件不满足时可使用等待队列来阻塞进程直到条件成立时唤醒。

1.定义和初始化等待队列

定义:wait_queue_head_t  my_queue;

初始化:init_waitqueue_head(&my_queue);

或者定义的同时初始化:DECLARE_WAIT_QUEUE_HEAD(my_queue);

2.进入等待队列,睡眠

wait_event(queue,condition)

当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上

wait_event_interruptible(queue,condition)

当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。

int wait_event_killable(queue, condition)

当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。

3.从等待队列中唤醒进程

wake_up(wait_queue_t *q)

从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有进程。

wake_up_interruptible(wait_queue_t *q)

从等待队列q中唤醒状态为TASK_INTERRUPTIBLE 的进程

可中断的睡眠状态的进程会睡眠直到某个条件变为真,比如说产生一个硬件中断、释放进程正在等待的系统资源或是传递一个信号都可以是唤醒进程的条件。不可中断睡眠状态与可中断睡眠状态类似,但是它有一个例外,那就是把信号传递到这种睡眠状态的进程不能改变它的状态,也就是说它不响应信号的唤醒。不可中断睡眠状态一般较少用到,但在一些特定情况下这种状态还是很有用的,比如说:进程必须等待,不能被中断,直到某个特定的事件发生。

四、工作队列的使用

工作队列:是一种将任务推后执行的形式,它把推后的任务交由一个内核线程去执行。这些工作组成的队列叫做工作队列,这些工作允许重新调度甚至睡眠。中断进行上半部分的程序处理时处理器会屏蔽外部中断,而在下半部分开始工作之前会打开处理器对外部中断的响应。

Linux内核使用struct workqueue_struct来描述一个工作队列:

struct workqueue_struct{

struct cpu_workqueue_struct *cpu_wq;

struct list_head list;

const char *name; /*workqueue name*/

int singlethread;

int freezeable; /* Freeze threads during suspend */

int rt;

};

Linux内核使用struct work_struct来描述一个工作项:

struct work_struct{

atomic_long_t data;

struct list_headentry;

work_func_t func;

};

typedef void (*work_func_t)(struct work_struct *work);

工作的创建过程:

1.创建工作队列

struct workqueue_struct *create_workqueue(const char *name);

如:my_wq = create_workqueue("my_wq");

2.创建工作

INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);

如:work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);

INIT_WORK(work1,work1_fun);

3.提交工作

int queue_work(struct workqueue_struct *,struct work_struct *work);

如:queue_work(my_wq,work1);

挂载工作后工作并不一定会立刻运行,只有在线程觉得cpu比较空闲时才会运行。另外。在大多数情况下, 驱动并不需要己建立工作队列,只需定义工作, 然后将工作提交到内核已经定义好的工作队列keventd_wq。

1.提交工作到默认队列

schedule_work(struct work_struct * );

如:schedule_work(work1);

五、内核定时器的使用

linux中使用struct timer_list来描述一个定时器结构体变量

struct timer_list{

struct list_head entry;

unsigned long expires;

void (*function)(unsigned long);

unsigned long data;

struct tvec_base *base;

};

2.初始化定时器

init_timer初始化

如:init_timer(&buttons_timer);

设置超时函数

如:buttons_timer.function  = buttons_timer_function;

3.add_timer注册定时器

如:add_timer(&buttons_timer);

4.mod_timer启动定时器

如:mod_timer(&buttons_timer, jiffies + (HZ /10));

(HZ代表1个滴答,jiffies代表的是系统最近一次启动以来的滴答数,以秒计)。

定时器只是阻塞当前进程。

六、向linux内核中添加外部中断驱动模块

由于混杂设备是一种特殊的字符设备,所以混杂设备的驱动也存放于/drivers/char下,具体的步骤为:

1.将mini2440_remote.c放到/drivers/char目录下

2.修改/drivers/char/kconfig文件,添加:

 config MINI2440_REMOTE
tristate "Remote Driver for FriendlyARM Mini2440 development boards"
depends on MACH_MINI2440
default y if MACH_MINI2440
help
this is remote driver for "Navigation Boat Project",written by luo jie at JiangSu University.

其中,tristate表示“三态”,即Y、N、M。

3.修改/drivers/char/Makefile文件,添加:

obj-$(CONFIG_MINI2440_REMOTE) +=mini2440_remote.o

修改成功后使用make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-命令可以看到配置选项。

七、完整驱动程序代码:

 /******************************************************************************
*文件名: Remote_Driver.c
*文件功能 遥控外部中断内核驱动程序
*作者: 罗杰(E-Mail:1454760043@qq.com),2015年10月19日于江苏大学
*修改记录:
******************************************************************************/
#include"linux/module.h"
#include"linux/init.h"
#include <linux/kernel.h>
#include <linux/fs.h>
#include "linux/io.h"
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/gpio.h> #define GPFCON 0x56000050
#define GPFDAT 0x56000054
#define GPGCON 0x56000060
#define GPGDAT 0x56000064 unsigned int *gpio_config;
unsigned int w_data;
unsigned char b_data; static volatile int flag = ;
//等待队列
wait_queue_head_t wait_for_interrupt;
//工作队列
static struct work_struct *judge_interrupt;
//定时器
static struct timer_list wipe_shaking_timer; //中断函数中提交的工作队列,中断处理函数下半部分
void My_Work(void )
{
mod_timer(&wipe_shaking_timer,jiffies + (HZ / ));
} //中断消抖函数,中断处理函数的下半部分
void Wipe_Shaking()
{
w_data = readw(gpio_config); if((w_data & 0x1) == )
{
printk("In Remote_Driver:外部中断0下降沿触发!\n");
wake_up_interruptible(&wait_for_interrupt);
flag = ;
}
else
{
printk("In Remote_Driver:外部中断0未发生中断!\n");
flag = ;
} } //初始化I/O端口
void Io_Init()
{ //设置GPF0为中断工作方式,设置GPF1-GPF6,GPG0-GPG1为I/O输出引脚,所有引脚输出低电平
gpio_config = ioremap(GPFCON,);
w_data = readw(gpio_config);
w_data &= ~(0x3fff);
w_data |= 0x1556;
writew(w_data,gpio_config); gpio_config = ioremap(GPFDAT,);
b_data = readb(gpio_config);
b_data &= ~(0x7f);
b_data |= 0x0;
writeb(b_data,gpio_config); gpio_config = ioremap(GPGCON,);
w_data = readw(gpio_config);
w_data &= ~(0xf);
w_data |= 0x5;
writew(w_data,gpio_config); gpio_config = ioremap(GPGDAT,);
w_data = readw(gpio_config);
w_data &= ~(0x3);
w_data |= 0x0;
writew(w_data,gpio_config); } //遥控端口中断处理函数
irqreturn_t Remote_irq(int irq,void *dev_id)
{
//提交中断下半部工作
schedule_work(judge_interrupt); //中断返回
return IRQ_HANDLED;
} //设备文件打开函数
static int Remote_Open(struct inode *inode,struct file *file)
{
int ret = ; flag = ; //初始化I/O端口
Io_Init(); gpio_config = ioremap(GPFDAT,);
//注册中断处理函数
ret = request_irq(IRQ_EINT0,Remote_irq,IRQ_TYPE_EDGE_BOTH,"Remote_Driver",(void *));
if(ret == )
{
printk("In Remote_Driver:注册中断服务程序成功!\n"); }
else
{
printk("In Remote_Driver:无法注册中断服务程序!\n");
return -;
} //工作队列初始化,由中断处理函数提交
judge_interrupt = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(judge_interrupt,My_Work); //定时器初始化及注册,进行中断的消抖处理
init_timer(&wipe_shaking_timer);
wipe_shaking_timer.function = Wipe_Shaking;
add_timer(&wipe_shaking_timer); //等待队列,用来对read()操作进行阻塞
init_waitqueue_head(&wait_for_interrupt); return ;
} //设备文件的读取函数
ssize_t Remote_Read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{ printk("阻塞read进程!\n");
wait_event_interruptible(wait_for_interrupt,flag);
copy_to_user(buf, &flag, );
printk("读取数据成功!\n"); return ;
} //设备文件的关闭函数
int Remote_Close(struct inode* inode,struct file* file)
{
//注销中断函数
free_irq(IRQ_EINT0,(void *));
return ;
} //定义并初始化设备文件操作函数集
static struct file_operations remote_fops =
{
.open = Remote_Open,
.read = Remote_Read,
.release = Remote_Close,
}; //定义一个混杂设备结构并初始化
static struct miscdevice remote_miscdev =
{ .minor = ,
//名称可以使用特殊字符
.name = "Remote_Driver",
.fops = &remote_fops,
}; //驱动设备初始化函数
static int __init Remote_Init()
{
int ret;
//注册混杂设备
ret = misc_register(&remote_miscdev);
if(ret != )
{
printk("In Remote_Driver:无法注册混杂设备!\n");
return -;
}
else
{
printk("In Remote_Driver:成功注册混杂设备!\n");
} //若初始化成功则必须返回0
return ; } //驱动设备退出函数
static void __exit Remote_Exit()
{
//注销混杂设备
misc_deregister(&remote_miscdev);
} //模块初始化,仅当使用insmod/podprobe命令加载时有用,如果设备不是通过模块方式加载则此语句不会被执行
module_init(Remote_Init);
//卸载模块,仅当使用insmod/podprobe命令加载时有用,如果设备不是通过模块方式加载则此语句不会被执行
module_exit(Remote_Exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("罗杰(E-mail:1454760043@qq.com)");

向linux内核中添加外部中断驱动模块的更多相关文章

  1. 在Linux内核中添加系统调用,并编译内核

    1 环境准备 运行系统:vmware下安装的ubuntu10.10 32bit桌面版. 编译内核版本: linux-2.6.32.63 内核目录: /home/wanchouchou/linuxKer ...

  2. Linux内核中的中断栈与内核栈的补充说明【转】

    转自:http://blog.chinaunix.net/uid-12461657-id-3487463.html 原文地址:Linux内核中的中断栈与内核栈的补充说明 作者:MagicBoy2010 ...

  3. Linux内核中双向链表的经典实现

    概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核 ...

  4. Linux内核中的GPIO系统之(3):pin controller driver代码分析

    一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...

  5. Linux内核中流量控制

    linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下:而应用层上的控制是通过iproute2软件包中的tc来实现, tc和sched的关系就好象iptables和netfi ...

  6. Linux内核中SPI总线驱动分析

    本文主要有两个大的模块:一个是SPI总线驱动的分析 (研究了具体实现的过程): 另一个是SPI总线驱动的编写(不用研究具体的实现过程). 1 SPI概述 SPI是英语Serial Peripheral ...

  7. Linux内核中Makefile、Kconfig和.config的关系(转)

    我们在编译Linux内核时,往往在Linux内核的顶层目录会执行一些命令,这里我以RK3288举例,比如:make firefly-rk3288-linux_defconfig.make menuco ...

  8. Linux内核中的软中断、tasklet和工作队列具体解释

    [TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...

  9. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...

随机推荐

  1. IIS7报错

    错误内容:”未能加载文件或程序集“IWMS_Admin”或它的某一个依赖项.试图加载格式不正确的程“ 解决方法:进入IIS“应用程序池”,然后在右边列表中,选中当前网站所使用的程序池,打开右侧的“高级 ...

  2. [Java] 使用Java Visual VM寻找PermGen Space的解决办法

    在Eclipse使用tomcat运行3个项目时,老是报这个错误,以下为错误详情: 2014-5-28 13:47:41 org.apache.catalina.core.StandardWrapper ...

  3. Codeforces Beta Round #77 (Div. 1 Only) C. Volleyball (最短路)

    题目链接:http://codeforces.com/contest/95/problem/C 思路:首先dijkstra预处理出每个顶点到其他顶点的最短距离,然后如果该出租车到某个顶点的距离小于等于 ...

  4. jQuery基础知识点(DOM操作)

    1.样式属性操作     1)设置样式属性操作         ①设置单个样式: // 第一个参数表示:样式属性名称 // 第二个参数表示:样式属性值 $(selector).css(“color”, ...

  5. js日期格式化

    <html> <head> <script> function test(){ //Js获取当前日期时间及其它操作 var myDate = new Date(); ...

  6. html5 head头标签

    桌面端开发中,meta标签通常用来为搜索引擎优化(SEO)及 robots定义页面主题,或者是定义用户浏览器上的cookie:它可以用于鉴别作者,设定页面格式,标注内容提要和关键字:还可以设置页面使其 ...

  7. mysql注入小测试

    转自:http://www.jb51.net/article/46163.htm 在开发网站的时候,出于安全考虑,需要过滤从页面传递过来的字符.通常,用户可以通过以下接口调用数据库的内容:URL地址栏 ...

  8. python中的变量和数据类型

    一.变量定义:变量是计算机内存中的一块区域,存储规定范围内的值,值 可以改变,通俗的说变量就是给数据起个名字. 二.变量命名规则: 1. 变量名由字母.数字.下划线组成 2. 数字不能开头 3. 不可 ...

  9. 《DSP using MATLAB》示例Example4.3 双边序列

  10. 对NLP的一些新认识

    其实这是老板让上交的一份总结,贴出来,欢迎朋友们批评指正. 最近看了一部分关于NLP的几篇论文,其中大部分为神经网络实现, 从基本的HMM算法实现,到LSTM实现,有很多方法可以用来处理NLP任务中的 ...