Zephyr中断系统--使用

Creative Commons
本作品采用知识共享署名

本文说明如何使用Zephyr中断系统。

注册ISR

注册通用的ISR

注册的通用ISR会被放到软中断表中,ISR每次执行后退出都会引发调度,有两种方法可以使用

1. 编译期注册

当所有参数在编译阶段可确定时使用IRQ_CONNECT宏, 目前Zephyr大多数驱动安装ISR都是使用该方法
原型
IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p)
参数解释
irq_p: IRQ号,一般在dts中决定
priority_p: IRQ优先级一般在dts中决定.
isr_p: isr函数.
isr_param_p: isr执行时,传入isr函数的参数.
flags_p: 和架构相关的flag, 一般情况不使用.
返回
IRQ_CONNECT返回值就是irq_p
示例
以rt系列的uart1为例,其dts定义

1
2
3
4
5
6
7
8
uart1: uart@40184000 {
compatible = "nxp,kinetis-lpuart";
reg = <0x40184000 0x4000>;
interrupts = <20 0>;
clocks = <&ccm IMX_CCM_LPUART_CLK 0x7c 24>;
label = "UART_1";
status = "disabled";
};

其中interrupts中20是IRQ号,0是优先级,该dts的定义会被转为

1
2
DT_NXP_KINETIS_LPUART_UART_1_IRQ_0=20
DT_NXP_KINETIS_LPUART_UART_1_IRQ_0_PRIORITY=0

在驱动zephyr/drivers/serial/uart_mcux_lpuart.c中安装时引用,

1
2
3
4
5
6
7
8
static void mcux_lpuart_config_func_1(struct device *dev)
{
IRQ_CONNECT(DT_NXP_KINETIS_LPUART_UART_1_IRQ_0,
DT_NXP_KINETIS_LPUART_UART_1_IRQ_0_PRIORITY,
mcux_lpuart_isr, DEVICE_GET(uart_1), 0); //ISR函数为mcux_lpuart_isr,参数是uart1的device handle

irq_enable(DT_NXP_KINETIS_LPUART_UART_1_IRQ_0);
}

2.动态注册

当有参数在编译阶段不能确定时使用irq_connect_dynamic在运行时注册ISR, 目前zephyr只有极少数驱动
使用该方式,并且看流程也并不是必须要用irq_connect_dynamic动态注册,这里就只列出使用方法,不做示例了

原型
static inline int
irq_connect_dynamic(unsigned int irq, unsigned int priority,
void (routine)(void parameter), void *parameter,
u32_t flags)

参数
irq: IRQ号,一般在dts中决定
priority: IRQ优先级,一般在dts中决定,也可动态修改.
routine: isr函数.
parameter: isr执行时,传入isr函数的参数.
flags: 和架构相关的flag, 一般情况不使用.
返回值
返回IRQ号

注册直接的ISR

直接的ISR顾名思义,就是IRQ发生时ISR会被直接调用,是实际的将ISR函数地址放入到硬件指向的中断向量表,因此和普通的ISR比会有下面的不同

  • 不支持ISR参数
  • 只能使用中断堆栈
  • ISR退出时可选是否切换上下文
  • ISR退出时可选是否退出IDLE省电状态
  • 中断锁状态不改变

原型
IRQ_DIRECT_CONNECT(irq_p, priority_p, isr_p, flags_p)
参数
irq_p: IRQ号,一般在dts中决定
priority_p: IRQ优先级一般在dts中决定.
isr_p: isr函数.
flags_p: 和架构相关的flag, 一般情况不使用.
返回
返回值就是irq_p
示例
直接的ISR使用主要是为了降低ISR延迟,在现有的zephyr系统中,用得不多,目前看见的有蓝牙的link layer和gecko的counter在用, 如下

1
2
3
IRQ_DIRECT_CONNECT(RADIO_IRQn, CONFIG_BT_CTLR_WORKER_PRIO,
radio_nrf5_isr, 0);
irq_enable(RADIO_IRQn);

直接ISR的函数需要通过ISR_DIRECT_DECLARE声明后才能被CONNECT,如下

1
SR_DIRECT_DECLARE(radio_nrf5_isr)

使用直接ISR时,在idle状态下ISR发生时会退出idle,但ISR退出时不会主动退出idle状态就直接进入到睡眠状态,我们可以使用SR_DIRECT_PM退出idle状态,例如

1
2
3
4
5
6
7
8
9
ISR_DIRECT_DECLARE(radio_nrf5_isr)
{

isr_radio();

ISR_DIRECT_PM();

return 1;
}

可以从前面的示例看出,无论那种方式注册isr,最后都需要用irq_enable来使能isr,该函数会在后面的章节说明.

控制

irq_lock() ISR中调用会锁住所有中断,线程中调用只会在当前线程下锁住所有中断,返回一个key值,unlock时需要传递该key值
irq_unlock(key) 解锁指定key值,lock可已多次执行会被计数引用,unlock需要执行对应的次数才能真正的解锁
irq_enable(irq) 使能指定irq
irq_disable(irq) 禁止指定的irq

状态

irq_is_enabled(irq) 检查指定的irq是否被使能
bool k_is_in_isr(void) 检查当前代码是否执行在isr中,如果是返回true,如果执行在线程中返回false
int k_is_preempt_thread(void) 检查当前代码是否执行在可抢占式线程中,如果是返回非0,如果执行在ISR或者协程中返回0
static bool k_is_pre_kernel(void) 检查当前代码是否执行在启动阶段,如果是post-kernel阶段之前返回true, 如果是post-kernel期间或者之后返回false

参考

https://docs.zephyrproject.org/latest/reference/kernel/other/interrupts.html