linux驱动-中断

每个中断都有一个中断号(线),通过中断号即可区分不同的中断。

  • 如果要处理的内容不希望被其他中断打断,那么可以放到上半部
  • 如果要处理的任务对时间敏感,可以放到上半部
  • 如果要处理的任务与硬件有关,可以放到上半部
  • 其他任务,优先考虑放到下半部

上半部###

request_irq 函数####

在 Linux 内核中要想使用某个中断是需要申请的,request_irq 函数用于申请中断并使能,request_irq
函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。
int request_irq(unsigned int irq,irq_handler_t handler,
unsigned long flags,const char *name,void *dev)
irq:要申请中断的中断号。
handler:中断处理函数
flags:中断标志,可以在文件 include/linux/interrupt.h查看
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev:如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev设置为
设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数
返回值: 0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了

free_irq 函数####

使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。
void free_irq(unsigned int irq,void *dev)
irq: 要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断,共享中断只有在释放最后中断处理函数的时候才会被禁止掉

中断处理函数####

irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。第二个参数需要与 request_irq 函数的 dev 参数保持一致
返回值为 irqreturn_t 类型,一般返回值使用如下形式:return IRQ_RETVAL(IRQ_HANDLED);
enum irqreturn {
   IRQ_NONE = (0 << 0),
   IRQ_HANDLED = (1 << 0),
   IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;

中断使能与禁止函数####

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)要等到当前正在执行的中断处理函数执行完才返回
void disable_irq_nosync(unsigned int irq)函数调用以后立即返回
local_irq_enable()全局开中断
local_irq_disable()全局关中断,但是多任务下可能容易崩溃
local_irq_save(flags)
local_irq_restore(flags)
这两个函数是一对, local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags内,local_irq_restore 用于恢复中断,将中断到 flags 状态。

下半部###

软中断####

软中断必须在编译的时候静态注册。Linux 内核使用 softirq_init 函数初始化软中断,默认会打开tasklet_action和tasklet_hi_action。
参数一般定义在include/linux/interrupt.h

注册#####
void open_softirq(int nr, void (*action)(struct softirq_action *))
nr:要开启的软中断,位于数组softirq_vec中
action:软中断对应的处理函数
使能#####
void raise_softirq(unsigned int nr)
nr:要触发的软中断

tasklet####

是利用软中断来实现的另一种下半部机制,建议使用tasklet。

定义一个tasklet#####
tasklet_init()初始化#####
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),\
                  unsigned long data);
t:要初始化的 tasklet
func:tasklet 的处理函数。
data:要传递给 func 函数的参数

也可以使用宏DECLARE_TASKLET来一次性完成tasklet的定义和初始化,name就是一个tasklet_struct类型的时候变量,func就是tasklet的处理函数,data是传递给func函数的参数。
DECLARE_TASKLET(name, func, data)

tasklet_schedule#####

在上半部,也就是中断处理函数中调用tasklet_schedule函数就能使tasklet在合适的时间运行
void tasklet_schedule(struct tasklet_struct *t)
t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name

工作队列####

Linux 内核使用work_struct结构体表示一个工作,这些工作组织成工作队列,工作队列使用workqueue_struct结构体表示,使用工作者线程worker thread来处理工作队列中的各个工作,使用worker结构体表示工作者线程。工作队列的上下文是内核线程,因此可以调度和睡眠。

定义#####

关于工作队列和工作者线程我们基本不用去管。直接定义一个work_struct结构体变量即可

初始化#####

使用INIT_WORK宏来初始化工作
#define INIT_WORK(_work, _func)
_work 表示要初始化的工作,
_func 是工作对应的处理函数
也可以使用DECLARE_WORK宏一次性完成工作的创建和初始化
#define DECLARE_WORK(n, f)
n表示定义的工作(work_struct)
f表示工作对应的处理函数

调度#####

工作的调度函数为schedule_work
bool schedule_work(struct work_struct *work)
work:要调度的工作。
返回值: 0成功,其他值失败。

设备树中断信息节点###

interrupt-cells,指定中断源的信息 cells 个数
interrupt-controller,表示当前节点为中断控制器
interrupts,指定中断号,触发方式等
interrupt-parent,指定父中断,也就是中断控制器

通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
dev: 设备节点
index:索引号,interrupts属性可能包含多条中断信息,通过index指定要获取的信息
返回值:中断号
如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号:
int gpio_to_irq(unsigned int gpio)
gpio: 要获取的 GPIO 编号
返回值: GPIO对应的中断号

定时器

其实就是基于软中断,然后在下半部执行

外部中断

posted @ 2021-12-23 15:24  月的光景  阅读(123)  评论(0编辑  收藏  举报