字符设备驱动 — 4 异常与中断

异常与中断

image-20240531195008754

中断属于异常的一种

异常会直接打断 CPU 的运行,而各种中断会被传到中断控制器,由中断控制器来选择优先级最高的中断并通知 CPU

处理流程

arm 对异常(中断)处理流程:

  1. 初始化:

    1. 设置中断源,让它可以产生中断
    2. 设置中断控制器(可以屏蔽中断、优先级)
    3. 设置 CPU 总开关(使能中断)
  2. 执行其他程序:正常程序

  3. 产生中断:比如按下按键 -> 中断控制器 -> CPU

  4. CPU 在每执行完一条指令都会检查 有/无 中断/异常 产生

  5. CPU 发现有 中断/异常 产生,开始处理

    对于不同的异常(中断),跳去不同的地址程序

  6. 这些函数做什么事情:

    1. 保护现场(各种寄存器)
    2. 处理异常(中断);分辨中断源,再调用不同的处理函数
    3. 恢复现场

3、4、5 都是由硬件完成的

异常向量表

异常向量表:异常向量表就是把所有异常类型码及其对应的异常向量(入口函数)按一定的规律存在一个区域内,这个存储区域就叫做异常向量表

中断向量表:把系统中所有的中断类型码(中断号)及其对应的中断向量(中断处理函数)按一定的规律存放在一个区域内,这个存储区域就叫中断向量表

中断向量表包含在异常向量表中

异常向量表中的 “ _irq ” 代表中断

异常向量表:

image-20240601093855130

异常向量表,每一条指令对应一种异常

发生复位时,CPU 就去执行第1条指令:b reset

发生中断时,CPU 就去执行 “ ldr pc,_irq ” 这条指令

这些指令存放的位置是固定的。比如对于 ARM9 芯片中断向量的地址是 0x18(这里指的是偏移位置),当然,向量表的位置并不总是从 0 地址开始,很多芯片可以设置某个 vector base 寄存器,指定向量表在其他位置,比如设置 vector base 为 0x80000000,指定为 DDR 的某个地址。但是表中的各个异常向量的偏移地址,是固定的:复位向量偏移地址是 0,中断是 0x18。

image-20240531202100596

查看

查看 /proc/interrupts 可以获取系统中的中断信息

第一列:中断号

第二列:向 CPU 产生中断的次数

再后面就是中断的信息

image-20240601161447658

中断函数

申请中断:

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
  • **irq:**要申请的硬件中断号

  • **handler:**中断处理函数(上半部)

    typedef irqreturn_t (*irq_handler_t)(int, void *);
    
  • **flags:**中断处理的属性

    1. IRQF_DISABLED:表示进入中断处理程序时禁止任何中断,包括其它CPU,默认只是屏蔽当前对应所有CPU的中断线

    2. IRQF_SHARED:表示中断线是共享的,也就是说同一个中断线上会对应多个中断处理程序,在中断处理程序中,通过第5个参数dev来判断到底是哪个发生了中断

  • **name:**中断名

  • **dev:**中断共享时用到,用来判断是哪个中断触发

释放中断:

void free_irq(unsigned int irq, void *dev_id)

irq:硬件中断号

dev_id:如果中断是共享的,则仅删除dev_id所对应的处理程序,直到最后一个中断被删除的时候才禁用此中断线

屏蔽中断:

void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

disable_irq 会等待中断处理完成,所以如果在中断的上半部调用,则会造成系统死锁,这种情况下,只能调用disable_irq_nosync

#define local_irq_save(flags) ...
void local_irq_disable(void);

这两个函数或宏将屏蔽 CPU 内所有的中断

与之对应的恢复所有的中断也有两个函数或宏

#define local_irq_restore(flags) ...
void local_irq_enable(void);

Liunx系统对于中断的处理

《中断学习(一) —— Linux中断流程以及处理机制》 - 一个不知道干嘛的小萌新 - 博客园 (cnblogs.com)

ARM程序的运行流程:

首先需要明白,ARM 芯片属于精简指令集计算机(RISC)

​ 特点:1)对于内存只有读、写指令

​ 2)对数据的运算是在 CPU 的内部完成的

​ 3)CPU 复杂度较小,易于设计

eg:a = a + b,在 ARM 中分为4步来实现:

  1. 把内存 a 的值读入 CPU 寄存器 R0
  2. 把内存 b 的值读入 CPU 寄存器 R1
  3. 把 R0、R1 的值相加,存入 R0
  4. 把 R0 的值写入内存 a

进程与线程的切换(保护现场):

进程、线程的核心是栈,栈是一种串列形状的数据结构,这种数据结构的特点是后入先出

程序被中断,如何保存现场:

  1. 程序A正常运行中,突然中断触发。程序A被打断这个时候会暂停程序A运行,将程序A中所有的寄存器保存下来。这也就保存现场(保存现场并不局限于中断,也包括程序之间的切换:函数调用)
  2. 这些寄存器会被保存在内存中,而这块内存就被称为栈
  3. 然后ARM去执行中断程序
  4. 等中断程序执行完了,从栈中恢复刚被保存的寄存器值

进程与线程之间的切换也大致如此

中断的拓展

Linux中有硬件中断和软件中断

对于硬件中断的两个原则:

  1. 不能嵌套(防止过多的保存现场导致栈溢出)(早期的 Linux 支持中断嵌套)
  2. 越快越好(上半部、下半部,上半部简单的接收数据(中断触发会被屏蔽,硬件自动屏蔽),将耗时的处理放在下半部(可以被其他硬件中断打断))(在中断处理的过程中是不能进行进程调度的,进程调度要靠定时器中断来实现,所以中断处理需要越快越好)

硬件中断

对于按键等硬件产生的中断称为 “ 硬件中断 ”,每个硬件中断都有自己的处理函数

当硬件触发中断后,会自动跳转到异常向量表的 irq 中,然后判断中断源,再跳转到对应的中断服务函数中,这些都是由硬件完成的

软件中断

人为制造的中断

软件中断何时产生:

由软件决定,对于 X 号软件中断,只需要把它的 flag 设置为 1 就表示发生了该中断

软件中断何时处理:

Linux系统中,各种硬件中断发生的很频繁,至少定时器中断每 10ms 发生1次(心跳),所以在处理完硬件中断后,再去处理软件中断

image-20240601103250695

  1. 硬件中断 A 处理过程中,没有其他中断产生:

    1. 中断 A 发生,count ++ 等于 1
    2. 硬件屏蔽中断,中断 A 上半部运行处理,处理完成,count – 等于 0
    3. count ++ 等于 1,硬件开启接收硬件中断,执行软件中断也就是所谓的中断下半部
    4. count – 等于 0,然后退出
  2. 硬件中断 A 处理过程中,又发生了中断 A:

    1. 第一次中断A发生,执行到第 ⑥ 时,一开中断后,中断 A 又再次使得 CPU 跳到中断向量表,这时 count 等于 1,并且中断A下半部的代码并未执行
    2. CPU 又从 ① 开始再次执行中断 A 的上半部代码,在 ① 中 count++ 等于2,到 ③ 后 count-- 等于1
    3. 在第 ④ 步发现 count 等于 1,所以直接结束当前第 2 次中断的处理
    4. 重点来了,第 2 次中断发生后,打断了第一次中断的第⑦步处理,当第 2 次中断处理完毕, CPU会继续去执行第 ⑦ 步(但是只会处理一次下半部)

    可以看到,发生 2 次硬件中断 A 时,它的上半部代码执行了 2 次,但是下半部代码只执行了一次

  3. 硬件中断 A 处理过程中,又发生了中断 B:

    1. 第一次中断A发生,执行到第 ⑥ 时,一开中断后,中断 B 又再次使得 CPU 跳到中断向量表,这时 count 等于 1,并且中断下半部的代码并未执行
    2. CPU 又从 ① 开始执行中断 B 的上半部代码,在 ① 中 count++ 等于2,到 ③ 后 count-- 等于1
    3. 在第 ④ 步发现 count 等于 1,所以直接结束当前第 2 次中断的处理
    4. 重点来了,第 2 次中断发生后,打断了第一次中断的第 ⑦ 步处理。当第 2 次中断处理完毕, CPU会继续去执行第 ⑦ 步

    在第 ⑦ 步里,它会去执行中断 A 的下半部,也会去执行中断 B 的下半部。 所以,多个中断的下半部,是汇集在一起处理的

中断不能休眠原因:

如果在中断的上半部睡眠,会发生什么:

在中断上半部执行的时候,硬件是屏蔽了中断接收的。所以操作系统接收不到任何中断(包括时钟中断),系统就会瘫痪(调度器依赖的时钟节拍也会没有)

如果在中断的下半部睡眠,会发生什么:

不能睡眠的关键是因为上下文
操作系统以进程调度为单位,进程的运行在进程的上下文中,以进程描述符作为管理的数据结构,进程可以睡眠的原因是操作系统可以切换不同进程的上下文,进行调度操作,这些操作都以进程描述符为支持,中断运行在中断上下文,没有一个所谓的中断描述符来描述它,它不是操作系统调度的单位,一旦在中断上下文中睡眠,首先无法切换上下文(因为没有中断描述符,当前上下文的状态得不到保存),其次,没有人来唤醒它,因为它不是操作系统的调度单位,此外,中断的发生是非常非常频繁的,在一个中断睡眠期间,其它中断发生并睡眠了,那很容易就造成中断栈溢出导致系统崩溃

注意:如果中断下半部使用的是工作队列机制,它是可以休眠的

执行中断下半部的三种机制

软中断

参考:

Linux中断子系统之软中断、tasklet和工作队列_linux 软中断-CSDN博客

初始化

内核中有一个软件中断数组

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

softirq_action 这个结构体用于描述一个软件中断,默认情况下,系统上只能使用 32 个软中断

struct softirq_action
{void (*action)(struct softirq_action *);
};

action是一个指向处理程序例程的指针,在软中断发生时由内核执行该处理程序例程

软件中断必须先注册,内核才能执行软中断,open_softirq 函数即用于该目的,它在 softirq_vec 表中指定的位置写入新的软中断

void open_softirq(int nr, void (*action)(struct softirq_action *))
{softirq_vec[nr].action = action;
}

软中断的类型是通过枚举静态定义的,当前有10种软中断类型,从0到9,0的优先级最高,当系统调度到软中断时会最先执行

enum
{HI_SOFTIRQ=0,        /* 高优先级tasklet软中断 */TIMER_SOFTIRQ,       /* 定时器软中断 */NET_TX_SOFTIRQ,      /* 发送网络包软中断 */NET_RX_SOFTIRQ,      /* 接收网络包软中断 */BLOCK_SOFTIRQ,       /* 用于处理块设备「block device」的软中断 */IRQ_POLL_SOFTIRQ,    /* 用于执行IOPOLL的回调函数 */TASKLET_SOFTIRQ,     /* tasklet软中断 */SCHED_SOFTIRQ,       /* 用于进程调度与负载均衡的软中断 */HRTIMER_SOFTIRQ,     /* 用于高精度定时器的软中断 */RCU_SOFTIRQ,         /* 用于RCU服务的软中断 */NR_SOFTIRQS
};

在 start_kernel 函数会调用 softirq_init(); 函数来初始化软件中断:

void __init softirq_init(void)
{.../* 注册tasklet相关的两个软中断 */open_softirq(TASKLET_SOFTIRQ, tasklet_action);open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
触发

系统通过一个 irq_cpustat_t 的数据结构来记录软中断触发与否,可以将其理解为软中断状态寄存器,该寄存器实际是一个 unsigned int 类型的 _softirq_pending 变量,32 bit 可以对应32个软中断,当然目前只使用了 bit0 到 bit9 这10个

typedef struct {unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;

触发软中断其实就是在 _softirq_pending 相应的bit位上写1,触发软中断的接口有两个 raise_softirq 和 raise_softirq_irqodd,前者在接口内部会关本地中断,而后者需要保证调用时已经处于关中断状态

inline void raise_softirq_irqoff(unsigned int nr)
{__raise_softirq_irqoff(nr);if (!in_interrupt())wakeup_softirqd();
}void raise_softirq(unsigned int nr)
{unsigned long flags;local_irq_save(flags);raise_softirq_irqoff(nr);local_irq_restore(flags);
}void __raise_softirq_irqoff(unsigned int nr)
{lockdep_assert_irqs_disabled();trace_softirq_raise(nr);or_softirq_pending(1UL << nr);
}
执行

因为每个cpu都有一个软中断状态寄存器__softirq_pending,因此每个CPU可以并行执行自己的软中断队列,软中断的执行时机有3个:

  1. 触发软中断后(执行raise_softirqraise_softirq_irqoff时);
  2. 硬中断退出时(irq_exit时);
  3. 任何调用local_bh_enable打开本地软中断的时机image-20240603094224628

有几种方法可开启软中断处理,但这些都归结为调用 do_softirq 函数

asmlinkage __visible void do_softirq(void)
{__u32 pending;unsigned long flags;if (in_interrupt())return;local_irq_save(flags);     /* 保存IF标志的状态值,并禁用本地CPU上的中断 */pending = local_softirq_pending();if (pending && !ksoftirqd_running(pending))do_softirq_own_stack();local_irq_restore(flags);  /* 恢复保存的IF标志的状态值并返回 */
}

in_interrupt() 该函数确认当前不处于中断上下文中(当然,即不涉及硬件中断),如果处于中断上下文,则立即结束,因为软中断用于执行 ISR 中非时间关键部分,所以其代码本身一定不能在中断处理程序内调用

通过 local_softirq_pending,确定当前 CPU 软中断位图中所有置位的比特位。如果有软中断等待处理,则调用 __do_softirq

asmlinkage __visible void __softirq_entry __do_softirq(void)
{unsigned long end = jiffies + MAX_SOFTIRQ_TIME;unsigned long old_flags = current->flags;int max_restart = MAX_SOFTIRQ_RESTART;    /* 循环计数器的值初始化为10 */struct softirq_action *h;bool in_hardirq;__u32 pending;int softirq_bit;/** Mask out PF_MEMALLOC as the current task context is borrowedfor the* softirq. A softirq handled, such as network RX, might set PF_MEMALLOC* again if the socket is related to swapping.*/current->flags &= ~PF_MEMALLOC;/* 将本CPU的__softirq_pending变量复制到局部变量中 */pending = local_softirq_pending();account_irq_enter_time(current);/* 关软中断,防止再次进入软中断执行逻辑 */__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);in_hardirq = lockdep_softirq_start();restart:/* 将__softirq_pending清零,以便可以激活新的软中断 */set_softirq_pending(0);/* 开启硬件中断 */local_irq_enable();h = softirq_vec;while ((softirq_bit = ffs(pending))) {unsigned int vec_nr;int prev_count;h += softirq_bit - 1;vec_nr = h - softirq_vec;prev_count = preempt_count();/* 对softirq的执行次数计数,这是/proc/softirqs的数据来源 */kstat_incr_softirqs_this_cpu(vec_nr);trace_softirq_entry(vec_nr);/* 某软中断对应的action回调 */h->action(h);trace_softirq_exit(vec_nr);if (unlikely(prev_count != preempt_count())) {pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",vec_nr, softirq_to_name[vec_nr], h->action,prev_count, preempt_count());preempt_count_set(prev_count);}h++;pending >>= softirq_bit;}if (__this_cpu_read(ksoftirqd) == current)rcu_softirq_qs();/* 处理完一波软中断后,关闭硬件中断 */local_irq_disable();/* 读一下当前的__softirq_pending,看这段时间有没有触发新的软中断 */pending = local_softirq_pending();/* 有新的软中断需要处理:* 如果时间没超过2ms && 当前没有进程需要调度 && 这种循环没有超过10次,* 此时重新跳到while循环处依次执行软中断的action回调;* 否则唤醒ksoftirqd线程执行软中断处理。*/if (pending) {if (time_before(jiffies, end) && !need_resched() &&--max_restart)goto restart;wakeup_softirqd();}lockdep_softirq_end(in_hardirq);account_irq_exit_time(current);/* 离开软中断上下文时,开启软中断 */__local_bh_enable(SOFTIRQ_OFFSET);WARN_ON_ONCE(in_interrupt());current_restore_flags(old_flags, PF_MEMALLOC);
}

__do_softirq函数只做固定时间或固定次数的循环,然后就返回了,避免出现延迟用户进程的执行,如果还有其余挂起的软中断,内核线程ksoftirqd将会在预期时间里处理它们

内核线程 ksoftirqd

系统中的每个处理器都分配了自身的守护进程,名为 ksoftirqd

static void wakeup_softirqd(void)
{/* Interrupts are disabled: no need to stop preemption */struct task_struct *tsk = __this_cpu_read(ksoftirqd);if (tsk && tsk->state != TASK_RUNNING)wake_up_process(tsk);
}

wake_up_process唤醒进程后运行run_ksoftirqd()函数

static void run_ksoftirqd(unsigned int cpu)
{local_irq_disable();if (local_softirq_pending()) {/** We can safely run softirq on inline stack, as we are not deep* in the task stack here.*/__do_softirq();local_irq_enable();cond_resched();return;}local_irq_enable();
}

tasklet(小任务)机制

tasklet 机制主要用在下半部要做的事耗时不是很长

tasklet 是基于软件中断来实现的,上面软件中断处理的流程图使用的就是 tasket 机制

image-20240601155015348

tasklet 的使用非常的简单,我们只需要定义 tasklet 及其处理函数并将它们将关联

// tasklet 结构体
struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};
void my_tasklet_func(unsigned long); // 定义一个处理函数DECLARE_TASKLET(my_tasklet, my_tasklet_fun, data); // 代码 DECLARE_TASKLET 实现了定义名称为	my_tasklet 的 tasklet 并将其与 my_tasklet_func 这个函数绑定,而传入这个函数的参数为 datatasklet_schedule(&my_tasklet) // 使系统在适当的时候进行调度运行

使用模板:

根据上面软件中断的那个流程图可以写出以下的使用模板

void xxx_do_tasklet(unsigned long);struct tasklet_struct xxx_tasklet;DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);void xxx_do_tasklet(unsigned long)
{……
}// 中断服务程序
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{ ……// 使系统在合适的时候调用驱动的下半部tasklet_schedule(&xxx_tasklet); ……
}int _init xxx_init(void)
{ …… // 申请中断,中断的上半部result = request_irq(xxx_irq,xxx_interrupt,IRQF_DISABLED,”xxx”,NULL);  ……return IRQ_HANDLED;
}void _exit xxx_exit(void)
{……free_irq(xxx_irq,xxx_irq_interrupt);……
}

工作队列机制

tasklet 机制在中断下半部执行的过程中,虽然是开中断的,期间可以处理各类中断,但是毕竟整个中断的处理还没有走完,这期间 APP 是无法运行的

如果中断要做的事情太过于耗时,就不能使用 tasket 机制(软件中断)来做,而应该用内核线程来做:在中断的上半部唤醒内核线程。这样内核线程和 APP 都一样竞争执行,APP 有机会执行,系统不会卡顿

上述的那个内核线程是系统帮我们创建的,一般是 kworker 线程,内核中有很多这样的线程:

image-20240602192915011

kworker 线程要去 “ 工作队列(work queue)”上取出一个一个 “ 工作(work)”,来执行它里面的函数

使用步骤:

  1. 创建 work:

    void my_func();
    DECLARE_WORK(my_work, my_func, &data); 
    // DECLARE_WORK(my_work, my_func);    // 第三个参数可以不要也可以写为空
    static DECLARE_WORK(aer_recover_work, aer_recover_work_func);
    // 在编译时创建函数,并将函数入口地址和参数地址赋给它
    

    如果不想要在编译时就用DECLARE_WORK ( ) 创建并初始化工作结构体变量,也可以在程序运行时再用INIT_WORK ( ) 创建

    void my_func();
    struct work_struct my_work;
    INIT_WORK(&my_work, my_func, &data); 
    
  2. 将 work 提交给 work queue

    schedule_work(&my_work); // 一般是中断的上半部调用
    

谁来调用 work:

不用我们管,schedule_work 函数不仅仅是把 work 放入队列,还会把 kworker 线程唤醒,此线程抢到时间运行时,它就会从队列中取出 work,执行里面的函数

使用模板:

struct work_struct xxx_wq;// 中断下半部
void xxx_do_work(unsigned long) 
{……
}// 中断上半部
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{…… schedule_work(&xxx_wq); // 将 work 放入工作队列,并且唤醒内核的 kworker 线程…… 
}int init(void)
{……result = request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,“xxx”,NULL); ……INIT_WORK(&xxx_wq,(void(*)(void*))xxx_do_work,NULL);…….
}void xxx_exit(void)
{……free_irq(xxx_irq,xxx_interrupt);……
}
新技术:threaded_irq

image-20240602203438962

你可以只提供 thread_fn,系统会为这个函数创建一个内核线程,发生中断时,内核线程就会执行这个函数

之前的 work 都是由同一个 worker 线程来处理,在单 CPU 系统中也只能忍着,但是在多个 CPU 的系统中,就会浪费资源

新技术 threaded_irq,为每一个中断创建一个内核线程,多个中断的内核线程可以分配到多个 CPU 上执行,这提高了效率

重要结构体

image-20240603095835751

irq_desc

image-20240603100113187

理解:

image-20240603100235670

外部中断1和外部中断n共享 GPIO 的B号中断,而多个 GPIO 又汇聚到 GIC 的A号中断,最后由GIC中断 CPU,那么软件的处理就是反过来的,先读取 GIC 得知A号中断,再细分出 GPIO 的B号中断,最后在判断出具体是哪一个外部设备

所以中断处理函数的来源有三:

  1. GIC 的处理函数(GIC:中断控制器)

    irq_desc[A].handle_irq 细分出中断 后B ,调用对应的 irq_desc[B].handle_irq

    显然中断 A 是 CPU 感受到的顶层的中断,GIC 中断 CPU 时,CPU 读取 GIC 状态得到中断 A

  2. GPIO 的处理函数

    比如对于GPIO模块向 GIC 发出的中断B ,它的处理函数是 irq_desc[B].handle_irq

    判断中断具体有哪一个外设产生

  3. 外部设备提供的处理函数

    判断设备是否发生了中断,如何处理中断

    对于共享中断,比如 GPIO 中断 B,它的中断来源可能有多个,每个中断源对应一个中断处理函数,所以 irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数,一旦程序确定发生了 GPIO 中断 B,那么就会从链表里把那些函数取出来,一一执行(并不判断是哪一个外部设备而是循环执行所有的处理函数,这些处理函数会自行判断自己是否产生了中断),这个链表就是 action 链表

image-20240603102029523

irq_action

image-20240603102122776

当调用 request_irq、request_threaded_irq 注册中断处理函数时,内核就会构造一个 irqaction 结构体,里面保存 name、dev_id 等,最重要的是 handler、thread_fn、thread

**handler:**中断处理的上半部

thread_fn: 对应一个内核线程 thread,当 handler 执行完毕,Linux 内核会唤醒对应的内核线程,在内核线程里,会调用 thread_fn 函数

dev_id:

  1. 中断处理函数执行时,可以使用

  2. 卸载中断时要传入 dev_id,这样才能在 action 链表中根据 dev_id 找到对应项

    所以在共享中断中必须提供 dev_id,非共享中断可以不提供

irq_data

image-20240603103614731

中转站,里面有 irq_chip 指针、irq_domain 指针、都是指向别的结构体

irq:软件中断号

hwirq:硬件中断号

GPIO 中断B是软件中断号,GPIO里的第X号中断,这是硬件中断号

irq_domain 会把本地的 hwirq 映射为全局的 irq,比如GPIO控制器里有第1号中断,UART模块里也有第1号中断,这两个 “ 第1号中断 ”是不一样的,它们属于不同的 “ 域 ” —— irq_domain

irq_domain

image-20240603104547632

如何在设备树中指定中断,设备树的中断如何被转换为 irq 时,irq_domain 将会起到极大的作用

eg:

在设备树中会有这样的属性:

interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;

它表示要使用 gpio1 里的第 5 号中断,hwirq 就是 5

但是我们在驱动中会使用 request_irq(irq,handler)这样的函数来注册中断,irq 是软件中断号

gpio1 对应的 irq_domain 会将 hwirq 转换为 irq

irq_domian 结构体中有一个 irq_domain_ops 结构体,里面有各种的操作函数

**xlate:**用来解析设备树的中断属性,提取出 hwirq、type 等信息

**map:**把 hwirq 转换为 irq

irq_chip

image-20240603105826107

我们在 request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的 irq_chip 里的函数帮我们使能了中断

我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用 irq_chip 中的相关函数

但是对于外部设备相关的清中断操作,还是需要我们自己做的

就像上面图里的 “ 外部设备 1 “、“ 外部设备 n ”,外设备千变万化,内核里可没有对应的清除中断操作

设备树指定中断

概述

image-20240603110249327

在硬件上,“ 中断控制器 ” 只有 GIC 这一个,但是在软件上也可以把 “ GPIO ” 称为 “ 中断控制器 ”

在设备树中,中断控制器必须有一个属性:interrupt-controller,表明它是 “ 中断控制器 ”

还必须有一个属性:#interrupt-cells,表明引用这个中断控制器的话需要多少个 cell

image-20240603144354783

如果中断控制器有级联关系 ,下级的中断控制器还需要表明它的 “ interrupt-parent ” 是谁

image-20240603144433586

interrupts 的第2个参数取值:(这个参数可以写为宏,也可以写为具体的值)

#define IRQ_TYPE_NONE           0
#define IRQ_TYPE_EDGE_RISING    1
#define IRQ_TYPE_EDGE_FALLING   2
#define IRQ_TYPE_EDGE_BOTH      (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH     4
#define IRQ_TYPE_LEVEL_LOW      8

新写法:interrupts-extended

一个“interrupts-extended”属性就可以既指定 “ interrupt-parent ”,也指定 “ interrupts ”

interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;

示例

image-20240603151550216

在代码中获取中断

对于platform_device

这类节点可以转换为 platform_device 结构体

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num);
// 第2个参数:获取资源的类型    IORESOURCE_IRQ:中断资源
// 第3个参数:这类资源中的哪一个

对于I2C、SPI设备

一个 I2C 设备会被转换为一个 i2c_client 结构体,中断号会保存在 i2c_client 的 irq 成员里

image-20240603152319275

一个 SPI 设备会被转换为一个 spi_device 结构体,中断号会保存在 spi_device 的 irq 成员里

image-20240603152403882

调用 of_irq_get 获得中断号

如果你的设备节点既不能转换为 platform_device,它也不是 I2C 设备,不是 SPI 设备,那么在驱动程序中可以自行调用 of_irq_get 函数去解析设备树,得到中断号

对于GPIO

eg

gpio-keys {compatible = "gpio-keys";pinctrl-names = "default";user {label = "User Button";gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;gpio-key,wakeup;linux,code = <KEY_1>;};
};
button->gpio = of_get_gpio_flags(pp, 0, &flags);
bdata->gpiod = gpio_to_desc(button->gpio);irq = gpiod_to_irq(bdata->gpiod);

按键中断驱动示例

设备树

gpio_keys_100ask {compatible = "100ask,gpio_key";gpios = <&gpio5 1 GPIO_ACTIVE_LOW&gpio4 14 GPIO_ACTIVE_LOW>;pinctrl-names = "default";pinctrl-0 = <&key1_100ask &key2_100ask>;};

image-20240603161822601

image-20240603161901910

驱动代码

/* 1. 从platform_device获得GPIO* 2. gpio=>irq* 3. request_irq*/
static int gpio_key_probe(struct platform_device *pdev)
{int err;struct device_node *node = pdev->dev.of_node;int count;int i;enum of_gpio_flags flag;unsigned flags = GPIOF_IN;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);count = of_gpio_count(node); // 计算 gpio 的引脚数目if (!count){printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);return -1;}gpio_keys_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);for (i = 0; i < count; i++){gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag); // 获取 gpio 里的第i个引脚if (gpio_keys_100ask[i].gpio < 0){printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);return -1;}gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;if (flag & OF_GPIO_ACTIVE_LOW)flags |= GPIOF_ACTIVE_LOW;err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);}for (i = 0; i < count; i++){err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_keys_100ask[i]);}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/144483.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

水经微图PC版5.0.0即将内测

让GIS更简单高效&#xff01; 水经微图&#xff08;以下称“微图”&#xff09;PC版5.0.0即将内测&#xff0c;这是一个基于WeMapEngine开发的全新版本。 关于什么是WeMapEngine&#xff0c;请从《WeMapEngine可快速构建的GIS应用功能》一文中了解。 微图5.0.0功能界面 水经…

【分享】“可恶”的运算放大器电容负载

他们说如果使用放大器驱动电容负载(图 1、CLOAD)&#xff0c;一个不错的经验是采用一个 50 或 100 欧的电阻器 (RISO) 将放大器与电容器隔开。这个附加电阻器可能会阻止运算放大器振荡。 图 1.支持电容负载的放大器可能需要在放大器输出与负载电容器之间连接一个电阻器。 使用…

STM32—I2C通信外设

1.I2C外设简介 STM32内部集成了硬件I2C收发电路&#xff0c;可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能&#xff0c;减轻CPU的负担支持多主机模型&#xff08;可变多主机&#xff09;支持7位/10位地址模式&#xff08;11110......)支持不同的通…

JavaWeb JavaScript 11.XML —— 配置文件

生活想埋没我&#xff0c;没想到我是颗种子 —— 24.9.19 一、XML 1.什么是XML XML是EXtensible Markup Languge的缩写&#xff0c;翻译过来就是可扩展标记语言。所以很明显&#xff0c;XML和HTML一样都是标记语言&#xff0c;也就是说它们的基本语法都是标签 可扩展 三个字…

OpenCV基础入门30讲(Python)——第二讲 图像色彩转换

常见的几种颜色类型介绍 1、彩色图像&#xff08;Color Image&#xff0c;BGR&#xff09; 数据类型&#xff1a;uint8通道数&#xff1a;3&#xff08;BGR&#xff1a;蓝色、绿色、红色&#xff09;描述&#xff1a;彩色图像有三个通道&#xff0c;每个通道的值范围是 0 到 …

【图书推荐】《Autodesk Inventor 2024入门与案例实战(视频教学版)》

本书重点 配套示例文件、PPT课件、教学视频、电子教案、课程标准、骄婿大纲、模拟试题、作者微信群答疑服务。 内容简介 《Autodesk Inventor 2024入门与案例实战&#xff1a;视频教学版》以Autodesk Inventor 2024为平台&#xff0c;重点介绍Autodesk Inventor 2024中文版的…

洗衣机制造5G智能工厂物联数字孪生平台,推进制造业数字化转型

洗衣机制造业作为传统制造业的重要组成部分&#xff0c;通过引入5G智能工厂物联数字孪生平台&#xff0c;加速推进自身的数字化转型进程。这一创新模式不仅极大地提升了生产效率&#xff0c;还深刻改变了产品的设计、生产、管理及运维流程&#xff0c;为行业带来了前所未有的竞…

[数据集][目标检测]手机识别检测数据集VOC+YOLO格式9997张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;9997 标注数量(xml文件个数)&#xff1a;9997 标注数量(txt文件个数)&#xff1a;9997 标注…

saltstack企业实战

saltstack官网最新文档 saltstack架构设计 saltstack 高可用方案&#xff1a;Salt官网是有 HARebalance minion配置里写多个master地址 failover&#xff08;syndic&#xff09; 架构 操作系统&#xff1a;CentOS7.6salt版本&#xff1a;3000.3 多master https://www.cn…

【贪心算法】贪心算法一

贪心算法一 1.柠檬水找零2.将数组和减半的最少操作次数3.最大数4.摆动序列 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.柠檬水找零 题目…

【2023工业异常检测文献】SimpleNet

SimpleNet:ASimpleNetworkforImageAnomalyDetectionandLocalization 1、Background 图像异常检测和定位主要任务是识别并定位图像中异常区域。 工业异常检测最大的难题在于异常样本少&#xff0c;一般采用无监督方法&#xff0c;在训练过程中只使用正常样本。 解决工业异常检…

无人机黑飞打击技术详解

随着无人机技术的普及&#xff0c;无人机“黑飞”&#xff08;未经授权或违反规定的飞行&#xff09;现象日益严重&#xff0c;对公共安全、隐私保护及重要设施安全构成了严重威胁。为有效应对这一挑战&#xff0c;各国政府和安全机构纷纷研发并部署了一系列无人机黑飞打击技术…

光控资本:沪指涨0.59%,酿酒板块大幅拉升,数字货币概念等活跃

19日早盘&#xff0c;两市首要指数全线拉升&#xff0c;深证成指、创业板指涨约1%&#xff1b;场内超4800股飘红。 截至午间收盘&#xff0c;沪指涨0.59%报2733.38点&#xff0c;深证成指涨1.25%&#xff0c;创业板指涨0.99%&#xff0c;两市估计成交4263亿元。 盘面上看&…

C++ 9.19

练习&#xff1a;要求在堆区申请5个double类型的空间&#xff0c;用于存储5名学生的成绩。请自行封装函数完成 1> 空间的申请 2> 学生成绩的录入 3> 学生成绩的输出 4> 学生成绩进行降序排序 5> 释放申请的空间 主程序中用于测试上述函数 #include<ios…

google map小叉号不显示

背景需求 需要在uniapp中接入google地图,研究了一番,都没有找到合适的,现在说一下教程。 效果图 前期工作 这两点缺一不可,否则你啥也看不到。 1、电脑安装L-O-U梯 用于访问G-OO-G-LE的API或者创建google map key。 2、手机安装L-O-U梯 用于显示google地图。我就是手…

项目生命周期的类型

‌项目生命周期的类型包括预测型生命周期、迭代型生命周期、增量型生命周期、适应型生命周期和混合型生命周期。 预测型生命周期&#xff08;或称为瀑布型生命周期&#xff09; 从名称中我们就可以看出&#xff0c;我们对行业和项目是非常了解的&#xff0c;可以预测到下一步…

观后感:《中国数据库前世今生》——时代变迁中的数据库崛起

最近观看了《中国数据库前世今生》纪录片&#xff0c;这部影片详细梳理了从1980年代至今&#xff0c;中国数据库技术发展的跌宕历程。作为一名程序员&#xff0c;这部纪录片让我不禁感慨数据库技术的飞速进步&#xff0c;也让我更深入地理解了数据库技术在我们日常生活中的重要…

计算机毕业设计:Java心理在线测评测试系统开题报告+源代码效果图

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

气膜场馆造价解析:来自气膜厂家的专业解答—轻空间

气膜场馆作为一种新型的建筑形式&#xff0c;凭借其独特的结构和材料优势&#xff0c;逐渐在体育、文旅、工业等领域崭露头角。相较于传统建筑&#xff0c;气膜场馆在造价方面具有显著的优势&#xff0c;尤其是在节能、环保、安全等方面表现突出。然而&#xff0c;气膜场馆的造…

简单题70.爬楼梯 (Java)2024920

问题描述&#xff1a; java&#xff1a; class Solution {public int climbStairs(int n) {int f1 1;int f2 2;if(n<2){return n;}for(int s 3;s < n;s){int t f1 f2;f1 f2;f2 t;}return f2;} }