Linux第84步_了解Linux中断及其函数

1、中断号

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

2、Linux中断API函数

需要包含头文件“#include <linux/interrupt.h>

1)、在使用某个中断功能的时候,需要执行“申请中断”

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

irq:要申请中断的中断号;

handler中断处理函数,当中断发生以后就会执行此中断处理函数;

fags:中断标志;可以在文件“include/linux/interrupt.h”里面查看所有的中断标志;

name:中断名字,设置以后可以在“/proc/interrupts”文件中看到对应的中断名字;

dev:如果将flags设置为IRQF_SHARED的话,dev用来区分不同的中断

一般情况下将dev设置为“设备结构体”,dev会传递给中断处理函数irg_handler_t的第二个参数。

返回值:0表示中断申请成功,如果返回“-EBUSY”的话表示中断已经被申请过了, 其他负值,表示中断申请失败。

注意:

在“中断服务程序”中,不能使用request_irq()函数;

在“禁止睡眠的代码段”中,不能使用request_irq()函数;

执行request_irq()函数可能会导致睡眠;

request_irq()函数会使能“中断”,不需要我们手动去使能中断。

2)、常用的中断标志

#define IRQF_SHARED     0x00000080

/*

多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq()函数的dev参数就是唯区分他们的标志;

*/

#define IRQF_TRIGGER_NONE 0x00000000  //无触发

#define IRQF_ONESHOT 0x00002000 //单次中断,中断执行一次就结束

#define IRQF_TRIGGER_RISING 0x00000001  //上升沿触发

#define IRQF_TRIGGER_FALLING 0x00000002  //下降沿触发

#define IRQF_TRIGGER_HIGH     0x00000004  //高电平触发

#define IRQF_TRIGGER_LOW     0x00000008  //低电平触发

3)、在不需要使用某个中断功能的时候,需要执行“释放中断”

void free_irq(unsigned int irq, void *dev)

irq:要释放的中断号。

dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

返回值:无。

4)、中断处理函数

irqreturn_t (*irq_handler_t) (int, void *)

int”:第1个参数中断号,和request_irq()函数的irq参数保持一致。

void *”:第2个参数是指向void型的指针,和request_irq()函数的dev参数保持一致。至于用于区分“共享中断”的不同设备,dev也可以是指向设备数据结构。

返回值:为irqreturn_t类型;

enum irqreturn {

IRQ_NONE     = (0 << 0),  /*中断没有被这个设备处理*/

IRQ_HANDLED  = (1 << 0),  /*中断被这个设备处理*/

IRQ_WAKE_THREAD   = (1 << 1),  /*处理程序请求唤醒处理程序线程*/

};

typedef enum irqreturn irqreturn_t;//将irqreturn起个别名叫irqreturn_t

typedef irqreturn_t (*irq_handler_t)(int, void *);

#define IRQ_RETVAL(x) ((x) ? IRQ_HANDLED : IRQ_NONE)

一般中断服务函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED);

5)、中断使能

void enable_irq(unsigned int irq)

irq:要使能的中断号;

需要包含头文件“interrupt.h

6)、中断禁止

void disable_irq(unsigned int irq)

irq:要禁止的中断号;

注意:

要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。

void disable_irq_nosync(unsigned int irq)

irq:要禁止的中断号;

注意:

disable_irq_nosync()调用以后,会立即返回,不会等待当前中断处理程序执行完毕。

7)、使能总中断和关闭总中断

local_irq_enable()  //打开全局中断,需要包含头文件“interrupt.h

local_irq_disable() //关闭全局中断,需要包含头文件“interrupt.h

local_irq_save(flags) //用于禁止中断,并且将中断状态保存在flags中;

local_irq_restore(flags) //用于恢复中断,将中断到flags状态

3、上半部和下半部

上半部:上半部就是“中断处理函数”,那些“处理过程较快,占用时间较短的中断程序”由上半部完成。

下半部:那些“处理过程比较耗时的中断服务程序”,放到“下半部”去执行。

4、根据实际情况将中断服务程序的代码放到上半部或下半部,通常依据如下:

1)、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。2)、如果要处理的内容对时间敏感,可以放到上半部。3)、如果要处理的内容与硬件有关,可以放到上半部

4)、如果要处理的内容比较耗时的中断服务程序”,则放到“下半部”去执行。

Linux内核提供了多种下半部机制:软中断,tasklet,工作队列。2.5版本的以前Linux内核是使用“bottom half”机制(BH机制)来实现“下半部”,了解一下。

Linux内核将中断程序分为“上半部和下半部”的目的就是实现中断处理函数的快进快出。

5、下半部机制

1)、软中断

软中断是一种下半部机制,要求推后的工作不能睡眠,需要包含头文件“#include <linux/interrupt.h>

注意:

软中断必须在编译的时候静态注册,使用softirq_init()初始化软中断。

①、softirq_action结构体如下:

struct softirq_action

{

void (*action)(struct softirq_action *);

};

enum {

HI_SOFTIRQ=0,    /* 高优先级软中断 */

TIMER_SOFTIRQ,   /* 定时器软中断 */

NET_TX_SOFTIRQ,  /* 网络数据发送软中断 */

NET_RX_SOFTIRQ,  /* 网络数据接收软中断 */

BLOCK_SOFTIRQ,

IRQ_POLL_SOFTIRQ,

TASKLET_SOFTIRQ, /* tasklet软中断 */

SCHED_SOFTIRQ,   /* 调度软中断 */

HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */

RCU_SOFTIRQ,     /* RCU软中断 */

NR_SOFTIRQS      /*NR_SOFTIRQS的值是10*/

};

const char * const softirq_to_name[NR_SOFTIRQS];

②、注册软中断处理函数

void open_softirq(int nr, void (*action)(struct softirq_action *))

nr:要开启的软中断。

action:软中断对应的处理函数

注意:需要包含头文件“interrupt.h

③、触发软中断

void raise_softirq(unsigned int nr)

nr:要触发的软中断。

注意:需要包含头文件“interrupt.h

④、软中断初始化函数

void softirq_init(void)

注意:需要包含头文件“interrupt.h

2)、tasklet

tasklet是一种下半部机制,要求推后的工作不能睡眠。在介于软中断和tasklet之间,建议大家使用tasklet,需要包含头文件“#include <linux/interrupt.h>

①、tasklet_struct结构体如下:

struct tasklet_struct

{

struct tasklet_struct *next;

unsigned long state;

atomic_t count;

void (*func)(unsigned long);

unsigned long data;

};

②、初始化tasklet

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);

t:要初始化的tasklet;

func:tasklet的处理函数;

data:要传递给func函数的参数;

注意:需要包含头文件“interrupt.h

DECLARE_TASKLET(name, func, data)

name:为要定义的tasklet名字,其实就是tasklet_struct类型的变量名;

func:就是tasklet的处理函数;

data:是传递给fnc函数的参数;、

注意:需要包含头文件“interrupt.h

tasklet调度函数:

void tasklet_schedule(struct tasklet_struct *t)

t:要调度的tasklet,也就是DECLARE_TASKLET(name, func, data)里面的name;

注意:需要包含头文件“interrupt.h

注意:

在上半部,也就是“中断处理函数”中调用tasklet_schedule()函数就能使 tasklet在合适的时间运行;

④、举例:

struct tasklet_struct testtasklet; /* 定义taselet */

/* tasklet处理函数 */

void testtasklet_func(unsigned long data)

{

/* tasklet具体处理内容 */

}

/* 中断处理函数 */

irqreturn_t test_handler(int irq, void *dev_id)

{

......

/* 调度tasklet */

tasklet_schedule(&testtasklet);

/*

在上半部,也就是中断处理函数中调用tasklet_schedule()函数使 tasklet在合适的时间运行;

*/

......

}

/* 驱动入口函数 */

static int __init xxxx_init(void)

{

......

tasklet_init(&testtasklet, testtasklet_func, data);

/* 初始化tasklet*/

t=&testtasklet,要初始化的tasklet;

func=testtasklet_func,tasklet的处理函数testtasklet_func();

data:要传递给func函数的参数;

request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);

irq=xxx_irq:要申请中断的中断号;

handler=test_handler:中断处理函数test_handler(),当中断发生以后就会执行此中断处理函数;

fags=0:中断标志;

name="xxx":中断名字,设置以后可以在“/proc/interrupts”文件中看到对应的中断名字;

dev=&xxx_dev:将dev设置为“设备结构体”,dev会传递给中断处理函数irg_handler_t的第二个参数。

返回值:0表示中断申请成功,其他负值,表示中断申请失败,如果返回-EBUSY的话表示中断已经被申请过了。

/* 注册中断处理函数 */

......

}

3)、工作队列

工作队列是一种下半部机制,它工作在进程的上下文处,将要推后的工作交给一个内核线程去执行,因此,允许工作队列进入睡眠或被重新调度

work_struct结构体表示一个“工作”,需要包含头文件“workqueue.h”,如下;

struct work_struct {

  atomic_long_t data;

  struct list_head entry;

  work_func_t func; /* 工作队列处理函数 */

};

workqueue_struct结构体表示“工作队列”,需要包含头文件“workqueue.h”,如下:

struct workqueue_struct {

  struct list_head pwqs;

  struct list_head list;

  struct mutex mutex;

  int work_color;

  int flush_color;

  atomic_t nr_pwqs_to_flush;

  struct wq_flusher *first_flusher;

  struct list_head flusher_queue;

  struct list_head flusher_overflow;

  struct list_head maydays;

  struct worker *rescuer;

  int nr_drainers;

  int saved_max_active;

  struct workqueue_attrs *unbound_attrs;

  struct pool_workqueue *dfl_pwq;

  char name[WQ_NAME_LEN];

  struct rcu_head rcu;

  unsigned int flags ____cacheline_aligned;

  struct pool_workqueue __percpu *cpu_pwqs;

  struct pool_workqueue __rcu *numa_pwq_tbl[];

};

worker结构体表示“工作者线程”,需要包含头文件“workqueue_internal.h” ,worker结构体内容如下:

struct worker {

  union {    struct list_head   entry;    struct hlist_node  hentry;  };  struct work_struct  *current_work;  work_func_t  current_func;  struct pool_workqueue  *current_pwq;  struct list_head  scheduled;  struct task_struct  *task;  struct worker_pool  *pool;  struct list_head  node;  unsigned long last_active;  unsigned int flags;  int id;  int sleeping;  char desc[WORKER_DESC_LEN];  struct workqueue_struct *rescue_wq;  work_func_t last_func;};

Linux内核使用“工作者线程(worker thread)”来处理工作队列中的各个工作,每个“工作者线程(worker)”都有一个“工作队列(workqueue_struct)”,它会处理属于自己工作队列中的所有“工作(work_struct)”。

在实际驱动开发中,我们只需要定义“工作(work_struct)”即可,关于工作队列和工作者线程我们基本不用去管。

创建“工作(work_struct)”很简单,直接定义一个work_struct结构体变量即可,然后使用INIT_WORK宏来初始化“工作(work_struct)”,也可以使用 DECLARE_WORK宏来一次性完成“工作(work_struct)”的创建和初始化。

INIT_WORK 宏定义,需要包含头文件“workqueue.h”,如下:

#define INIT_WORK(_work, _func)

_work:表示要初始化的工作;

_func:是工作对应的处理函数;

DECLARE_WORK宏定义,需要包含头文件“workqueue.h”,如下:

#define DECLARE_WORK(n, f)

n:表示定义的“工作(work_struct)

f:表示工作对应的处理函数

工作调度函数,需要包含头文件“workqueue.h”:

bool schedule_work(struct work_struct *work)

work:要调度的工作;返回值:0 成功,其他值 失败;

注意:

在“上半部”,也就是“中断处理函数”中调用schedule_work()函数就能使 “工作队列(workqueue_struct)”在合适的时间运行;

struct work_struct testwork;  /* 定义工作(work) */

/* work处理函数 */void testwork_func_t(struct work_struct *work){

  /* work具体处理内容 */}

/* 中断处理函数 */irqreturn_t test_handler(int irq, void *dev_id){

  .....

  schedule_work(&testwork);

  /* 调度work */

  //work=&testwork:要调度的工作;  //返回值:0 成功,其他值 失败;

  ......}

/* 驱动入口函数 */static int __init xxxx_init(void){

  ......

  INIT_WORK(&testwork, testwork_func_t);

  /* 初始化work */

  //_work=&testwork:表示要初始化的工作;

  //_func= testwork_func_t:是工作对应的处理函数;

  request_irq(xxx_irq,test_handler,0,"xxx",&xxx_dev);

  /* 注册中断处理函数 */

  //irq= xxx_irq:要申请中断的中断号;

  //Handler= test_handler:中断处理函数,当中断发生以后就会执行此中断处理函数;

  //fags=0:中断标志;

  //name="xxx":中断名字;

  //dev=&xxx_dev:将dev设置为“设备结构体”,dev会传递给中断处理函数irg_handler_t的第二个参数。

  //返回值:0表示中断申请成功,其他负值,表示中断申请失败,如果返回-EBUSY的话表示中断已经被申请过了。

  ......

}

6、GIC中断控制器

GIC是ARM公司给Cortex-A/R内核提供的一个中断控制器,类似 Cortex-M 内核中的NVIC;

7、GIC中断源分类:

1)、Shared Peripheral Interrupt,简写SPI,称为“共享中断”,即所有Core共享的中断。比如:GPIO中断、串口中断等,这些中断所有的Core都可以处理,不限定特定 Core;

2)、Private Peripheral Interrupt,简写PPI,称为“私有中断”。 GIC支持多核,每个核有自己专用的中断,且由指定的核心处理,这些中断就叫做私有中断;

3)、Software-generated Interrupt,简写SGI,称为“软件中断”,由软件触发引起的中断,通过向寄存器GICD_SGIR写入数据来触发,系统会使用SGI中断来完成多核之间的通信;

8、中断ID

每个中断源都有一个唯一的中断ID,每个CPU最多支持1020个中断ID,中断ID号为ID0~ID1019。其中IQ0~ID15分配给“软件中断SGI”,IQ16~ID31分配给“私有中断PPI”,ID32~ID1019分配给“共享中断SPI”。

9、外部中断和事件控制器EXTI

Extended interrupt and event controller简写EXTI,它是ST公司设计的,用来辅助GIC管理STM32MP1的相应中断。

1)、EXTI特性:

支持76个输入事件;

两个CPU内核都支持;

所有事件输入均可让CPU唤醒;

2)、EXTI异步输入事件:

①、可配置事件,其特性如下:

可选择的有效触发边沿;

中断挂起状态寄存器位;

单独的中断和事件生成屏蔽;

支持软件触发;

②、直接事件,其特性如下:

固定上升沿有效触发;

EXTI中无中断挂起状态寄存器位(中断挂起状态由生成事件的外设提供);

单独的中断和事件生成屏蔽;

不支持软件触发;

3)、STM32MP1的中断处理方式:

外设直接产生中断到“GIC中断控制器”,然后由“GIC中断控制器”通知“CPU内核”;GPIO或外设产生中断到“外部中断和事件控制器EXTI”,然后将信号提交给“GIC中断控制器”,再由“GIC中断控制器”通知“CPU内核”;GPIO或外设产生中断到“外部中断和事件控制器EXTI”,然后直接将中断信号提交给“CPU内核”;

4)、GPIO中断

GPIO中断是我们最常用的功能。STM32MP1的所有GPIO都有中断功能,每一组GPIO最多有16个IO,比如:PA0~PA15,因此、每组GPIO就有16个中断,这16个GPIO事件输入对应EXTI0~15,其中 PA0、PB0 等都对应 EXTI0;

5)、设备树绑定信息参考文档

①、“GIC中断控制器”的设备树绑定信息参考文档:

Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml

②、EXTI控制器的设备树绑定信息参考文档:

Documentation/devicetree/bindings/interrupt-controller/st,stm32-exti.txt

10、GIC控制器节点

Table 9. Register boundary addresses

在“stm32mp151.dtsi”文件中,“intc节点”就是“ GIC控制器节点”;

intc: interrupt-controller@a0021000 {

compatible = "arm,cortex-a7-gic";

#interrupt-cells = <3>;

/*使用GIC中断控制器需要用3个cells来描述一个中断*/

interrupt-controller;/*这是一个中断控制器*/

reg = <0xa0021000 0x1000>,

      <0xa0022000 0x2000>;

/*表示address=0xa0021000,length=0x1000,占4096个字节*/

/*GICD的起始地址为0xa0021000,结束地址为0xA0021FFF,合计4KB*/

/*表示address=0xa0022000,length=0x2000,占8192个字节*/

/*GICC的起始地址为0xa0022000,结束地址为0xA0023FFF,合计8KB*/

};

#interrupt-cells = <3>;

/*使用GIC中断控制器需要用3个cells来描述一个中断*/

第1个cells:中断类型,0表示“共享中断SPI”,1表示“私有中断PPI”。第2个cells:中断号

对于“共享中断SPI”来说中断号的范围为32~287(256 个);

对于“私有中断PPI”来说中断号的范围为 16~31,但是该cell描述的中断号是从 0开始。第3个cells:标志,bit[3:0]表示中断触发类型

bit[3:0]=1表示上升沿触发;

bit[3:0]=2表示下降沿触发;

bit[3:0]=4表示高电平触发;

bit[3:0]=8表示低电平触发;

bit[15:8]为“私有中断PPI”的CPU掩码;

11、SPI6节点

SPI6中断号和中断ID,见下表:

第1列的“Num”就是SPI6的中断号:86

第2列“ID”为118,ID = Num + 32

SPI6地址范围,见下表:

Table 117. STM32MP157 interrupt mapping for Cortex®-A7 GIC

SPI6的起始地址为0x5C001000,结束地址为0x5C0013FF,合计1KB

打开stm32mp151.dtsi,找到SPI6节点内容,如下:

spi6: spi@5c001000 {

#address-cells = <1>;

/*定义子节点的reg和ranges的addres长度为32个位*/

#size-cells = <0>;

/*表示子节点的reg和ranges的length占0个位,即没有length*/

compatible = "st,stm32h7-spi";

reg = <0x5c001000 0x400>;

/*表示address=0x5c001000,length=0x400,占1024个字节*/

/*SPI6的起始地址为0x5C001000,结束地址为0x5C0013FF,合计1KB*/

interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;

/*GIC_SPI表示共享中断SPI,86为中断号*/

/*中断触发类型IRQ_TYPE_LEVEL_HIGH*/

clocks = <&scmi0_clk CK_SCMI0_SPI6>;

resets = <&scmi0_reset RST_SCMI0_SPI6>;

dmas = <&mdma1 34 0x0 0x40008 0x0 0x0 0x0>,

       <&mdma1 35 0x0 0x40002 0x0 0x0 0x0>;

dma-names = "rx", "tx";

power-domains = <&pd_core>;

status = "disabled";

};

12、EXTI控制器节点

exti: interrupt-controller@5000d000 {

compatible = "st,stm32mp1-exti", "syscon";

interrupt-controller;

#interrupt-cells = <2>;

/*使用EXTI中断控制器需要用2个cells来描述一个中断*/

/*第1个cells:中断号;第2个cells:中断标志位*/

reg = <0x5000d000 0x400>;

/*表示address=0x5000d000,length=0x400,占1024个字节*/

/*EXTI的起始地址为0x5000d000,结束地址为0x5000D3FF,合计1KB*/

hwlocks = <&hsem 1 1>;

/* exti_pwr is an extra interrupt controller used for

 * EXTI 55 to 60. It's mapped on pwr interrupt

 * controller.

 */

exti_pwr: exti-pwr {

interrupt-controller;

#interrupt-cells = <2>;

interrupt-parent = <&pwr_irq>;

/*指定exti_pwr所有子节点的中断父节点为pwr_irq*/

st,irq-number = <6>;

};

};

#interrupt-cells = <2>;

/*使用EXTI中断控制器需要用2个cells来描述一个中断*/

第1个cells:中断号;第2个cells:中断标志位,bit[3:0]表示中断触发类型

13、GPIOA~GPIOK寄存器地址

Table 9. Register boundary addresses (continued)

pinctrl: pin-controller@50002000 {

#address-cells = <1>;

/*定义子节点的reg和ranges的addres长度为32个位*/

#size-cells = <1>;

/*定义子节点的reg和ranges的length长度为32个位*/

compatible = "st,stm32mp157-pinctrl";

ranges = <0 0x50002000 0xa400>;

        /*子节点寄存器起始地址为0*/

            /*父节点寄存器起始地址为0x50002000*/

            /*寄存器最大偏移地址为0xa400*/

interrupt-parent = <&exti>;

/*指定pinctrl所有子节点的中断父节点为exti*/

/*这样GPIO的中断就和EXTI联系起来*/

st,syscfg = <&exti 0x60 0xff>;

hwlocks = <&hsem 0 1>;

pins-are-numbered;

gpioa: gpio@50002000 {

gpio-controller;

/*指定gpioa节点是一个GPIO控制器*/

#gpio-cells = <2>;

/*定义描述使用一个gpio口需要提供2个指定的参数*/

interrupt-controller;

/* 指定gpioa节点为中断控制器, 其父节点为pinctrl,其中断为exti*/

#interrupt-cells = <2>;

/*interrupts属性第1个cell为某个IO在所处组的编号*/

/*第2个cell表示中断触发方式*/

reg = <0x0 0x400>;

/*表示address=0,length=0x400,占1024个字节*/

/*GPIOA的起始地址为(0x50002000+0),结束地址为(0x50002000+0+0x400-1) */

/*GPIOA的起始地址为0x50002000,结束地址为0x500023FF */

clocks = <&rcc GPIOA>;

st,bank-name = "GPIOA";

status = "disabled";

};

gpiob: gpio@50003000 {

gpio-controller;

/*指定gpiob节点是一个GPIO控制器*/

#gpio-cells = <2>;

/*定义描述使用一个gpio口需要提供2个指定的参数*/

interrupt-controller;

/* 指定gpiob节点为中断控制器, 其父节点为pinctrl,其中断为exti*/

#interrupt-cells = <2>;

/*interrupts属性第1个cell为某个IO在所处组的编号*/

/*第2个cell表示中断触发方式*/

reg = <0x1000 0x400>;

/*表示address=0x1000,length=0x400,占1024个字节*/

/*GPIOB的起始地址为(0x50002000+0x1000)*/

/*GPIOB的结束地址为(0x50002000+0x1000+0x400-1) */

/*GPIOB的起始地址为0x50003000,结束地址为0x500033FF */

clocks = <&rcc GPIOB>;

st,bank-name = "GPIOB";

status = "disabled";

};

gpioc: gpio@50004000 {

gpio-controller;

/*指定gpioc节点是一个GPIO控制器*/

#gpio-cells = <2>;

/*定义描述使用一个gpio口需要提供2个指定的参数*/

interrupt-controller;

/* 指定gpioc节点为中断控制器, 其父节点为pinctrl,其中断为exti*/

#interrupt-cells = <2>;

/*interrupts属性第1个cell为某个IO在所处组的编号*/

/*第2个cell表示中断触发方式*/

reg = <0x2000 0x400>;

/*表示address=0x2000,length=0x400,占1024个字节*/

/*GPIOC的起始地址为(0x50002000+0x2000)*/

/*GPIOC的结束地址为(0x50002000+0x2000+0x400-1) */

/*GPIOC的起始地址为0x50004000,结束地址为0x500043FF */

clocks = <&rcc GPIOC>;

st,bank-name = "GPIOC";

status = "disabled";

};

gpiod: gpio@50005000 {

gpio-controller;

/*指定gpiod节点是一个GPIO控制器*/

#gpio-cells = <2>;

/*定义描述使用一个gpio口需要提供2个指定的参数*/

interrupt-controller;

/* 指定gpiod节点为中断控制器, 其父节点为pinctrl,其中断为exti*/

#interrupt-cells = <2>;

/*interrupts属性第1个cell为某个IO在所处组的编号*/

/*第2个cell表示中断触发方式*/

reg = <0x3000 0x400>;

/*表示address=0x3000,length=0x400,占1024个字节*/

/*GPIOD的起始地址为(0x50002000+0x3000)*/

/*GPIOD的结束地址为(0x50002000+0x3000+0x400-1) */

/*GPIOD的起始地址为0x50005000,结束地址为0x500053FF */

clocks = <&rcc GPIOD>;

st,bank-name = "GPIOD";

status = "disabled";

};

gpioe: gpio@50006000 {

gpio-controller;

/*指定gpioe节点是一个GPIO控制器*/

#gpio-cells = <2>;

interrupt-controller;

/* 指定gpioe节点为中断控制器, 其父节点为pinctrl,其中断为exti*/

#interrupt-cells = <2>;

reg = <0x4000 0x400>;

clocks = <&rcc GPIOE>;

st,bank-name = "GPIOE";

status = "disabled";

};

gpiof: gpio@50007000 {

gpio-controller;

#gpio-cells = <2>;

interrupt-controller;

#interrupt-cells = <2>;

reg = <0x5000 0x400>;

clocks = <&rcc GPIOF>;

st,bank-name = "GPIOF";

status = "disabled";

};

gpiog: gpio@50008000 {

gpio-controller;

/*指定gpiogg节点是一个GPIO控制器*/

#gpio-cells = <2>;

interrupt-controller;

#interrupt-cells = <2>;

reg = <0x6000 0x400>;

clocks = <&rcc GPIOG>;

st,bank-name = "GPIOG";

status = "disabled";

};

gpioh: gpio@50009000 {

gpio-controller;

/*指定gpioh节点是一个GPIO控制器*/

#gpio-cells = <2>;

interrupt-controller;

#interrupt-cells = <2>;

reg = <0x7000 0x400>;

clocks = <&rcc GPIOH>;

st,bank-name = "GPIOH";

status = "disabled";

};

gpioi: gpio@5000a000 {

gpio-controller;

/*指定gpioi节点是一个GPIO控制器*/

#gpio-cells = <2>;

interrupt-controller;

#interrupt-cells = <2>;

reg = <0x8000 0x400>;

clocks = <&rcc GPIOI>;

st,bank-name = "GPIOI";

status = "disabled";

};

gpioj: gpio@5000b000 {

gpio-controller;

/*指定gpioj节点是一个GPIO控制器*/

#gpio-cells = <2>;

interrupt-controller;

#interrupt-cells = <2>;

reg = <0x9000 0x400>;

clocks = <&rcc GPIOJ>;

st,bank-name = "GPIOJ";

status = "disabled";

};

gpiok: gpio@5000c000 {

gpio-controller;

/*指定gpiok节点是一个GPIO控制器*/

#gpio-cells = <2>;

interrupt-controller;

#interrupt-cells = <2>;

reg = <0xa000 0x400>;

clocks = <&rcc GPIOK>;

st,bank-name = "GPIOK";

status = "disabled";

};

};

14、GPIOZ寄存器:

Table 9. Register boundary addresses (continued)

pinctrl_z: pin-controller-z@54004000 {

#address-cells = <1>;

/*定义子节点的reg和ranges的addres长度为32个位*/

#size-cells = <1>;

/*定义子节点的reg和ranges的length长度为32个位*/

compatible = "st,stm32mp157-z-pinctrl";

ranges = <0 0x54004000 0x400>;

/*指定子节点寄存器起始地址为0*/

/*父节点寄存器起始地址为0x50004000*/

/*地址长度为0x400*/

pins-are-numbered;

interrupt-parent = <&exti>;

st,syscfg = <&exti 0x60 0xff>;

hwlocks = <&hsem 0 1>;

gpioz: gpio@54004000 {

gpio-controller;

/*指定gpioz节点是一个GPIO控制器*/

#gpio-cells = <2>;

interrupt-controller;

#interrupt-cells = <2>;

/*interrupts属性第1个cell为某个IO在所处组的编号*/

/*第2个cell表示中断触发方式*/

reg = <0 0x400>;

/*表示address=0,length=0x400,占1024个字节*/

/*GPIOZ的起始地址为(0x50004000+0)*/

/*GPIOZ的结束地址为(0x50004000+0+0x400-1) */

/*GPIOZ的起始地址为0x50004000,结束地址为0x500043FF */

clocks = <&scmi0_clk CK_SCMI0_GPIOZ>;

st,bank-name = "GPIOZ";

st,bank-ioport = <11>;

status = "disabled";

};

};

timer {

compatible = "arm,armv7-timer";

interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,

 <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,

 <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,

 <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;

interrupt-parent = <&intc>;

always-on;

}

15、简单总结一下与中断有关的设备树属性信息:

①、#interrupt-cells,指定中断源的信息cells个数;

②、imnterrupt-controller,表示当前节点为中断控制器;

③、interrupts,指定中断号,触发方式等;

④、imnterrupt-parent,指定父中断,也就是中断控制器;

⑤、interrupts-extended,指定中断控制器、中断号、中断类型和触发方式;

举例:

interrupt-parent = <&gpiog>;/*指定父中断器为&gpiog*/

interrupts = <3 IRQ_TYPE_EDGE_BOTH>;

/*指定中断号为3,中断类型和触发方式为边沿触发*/

使用interrupts-extended可以代替上面的两句:

interrupts-extended = <&gpiog 3 IRQ_TYPE_EDGE_FALLING>;

/*指定中断控制器&gpiog、中断号为3、中断类型和触发方式下降沿触发*/

16、触发类型,位于文件“irq.h”中:

IRQ_TYPE_NONE            - default, unspecified type

IRQ_TYPE_EDGE_RISING - rising edge triggered

IRQ_TYPE_EDGE_FALLING - falling edge triggered

IRQ_TYPE_EDGE_BOTH - rising and falling edge triggered

IRQ_TYPE_LEVEL_HIGH - high level triggered

IRQ_TYPE_LEVEL_LOW - low level triggered

IRQ_LEVEL        - Interrupt is level type

17、打开stm32mpl57f-ev1-a7-examples.dts文件,里面有如下所示代码

test_keys {

compatible = "gpio-keys";

#address-cells = <1>;

/*定义子节点的reg和ranges的addres长度为32个位*/

#size-cells = <0>;

/*表示子节点的reg和ranges的length占0个位,即没有length*/

autorepeat;

status = "okay";

/* gpio needs vdd core in retention for wakeup */

power-domains = <&pd_core_ret>;

button@1 {

label = "PA13";

linux,code = <BTN_1>;

interrupts-extended = <&gpioa 13 IRQ_TYPE_EDGE_FALLING>;

/*指定中断控制器&gpioa、中断号为13、中断类型和触发方式IRQ_TYPE_EDGE_FALLING*/

status = "okay";

wakeup-source;

};

};

18、相关函数:

1)、获取中断号,需要包含文件“of_irq.h

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

dev:为设备节点,Linux内核使用device_node结构体来描述一个节点。

index:索引号,interrupts属性可能包含多条中断信息,通过index指定要获取的信息;

返回值:中断号;

获取GPIO中断号,需要包含文件“gpio.h

int gpio_to_irq(unsigned int gpio)

gpio:要获取的GPIO编号;返回值:GPIO对应的中断号

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java注解的实现原理

首先我们常用的注解是通过元注解去编写的&#xff0c; 比如&#xff1a; 元注解有Target 用来限定目标注解所能标注的java结构&#xff0c;比如标注方法&#xff0c;标注类&#xff1b; Retention则用来标注当前注解的生命周期&#xff1b;比如source&#xff0c;class&…

2024最全的Sora学习资料合集

2024最全的Sora学习资料合集&#xff0c;共5专题、30份资料。 点击前往星球下载地址&#xff08;文末领取优惠券&#xff09;&#xff1a;https://t.zsxq.com/186rJ8iZL 1、Sora专属提示词库 2、Sora专属教程 3、Sora学习文章 4、Sora提示词技巧 5、Sora-AI视频全网最全收集(…

Pycharm服务器配置python解释器并结合内网穿透实现公网远程开发

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

spring boot3自定义注解+拦截器+Redis实现高并发接口限流

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途 目录 写在前面 内容简介 实现思路 实现步骤 1.自定义限流注解 2.编写限流拦截器 3.注册拦截器 4.接口限流测试 写在前…

外包干了8天,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;19年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

Redis命令介绍

一、redis启动&#xff1a; 本地启动&#xff1a;redis-cli 远程启动&#xff1a;redis-cli -h host -p port -a password Redis 连接命令 1 AUTH password 验证密码是否正确 2 ECHO message 打印字符串 3 PING 查看服务是否运行 4 QUIT 关闭当前连接 5 SELECT index 切换…

JUC内容概述

复习概念 Sleep和Wait的区别 Sleep是Thread的静态方法&#xff0c;wait是Object的方法&#xff0c;任何对象实例都可以使用sleep不会释放锁&#xff0c;他也不需要占用锁&#xff0c;暂停。wait会释放锁&#xff0c;但是调用他的前提是线程占有锁他们都可以被Interrupted方法…

SQLite数据库文件损坏的可能几种情况(一)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;十三&#xff09; 下一篇&#xff1a;SQLite使用的临时文件&#xff08;二&#xff09; 概述 SQLite数据库具有很强的抗损坏能力。如果应用程序崩溃&#xff0c…

鸿蒙hdc使用指导

简介 hdc&#xff08;HarmonyOS Device Connector&#xff09;是HarmonyOS为开发人员提供的用于调试的命令行工具&#xff0c;通过该工具可以在windows/linux/mac系统上与真实设备或者模拟器进行交互。 环境准备 hdc工具通过HarmonyOS SDK获取&#xff0c;存放于SDK的toolch…

2024-3-28 市场情绪强修复

这一轮退潮负反馈都修复了&#xff0c; 艾艾精工 博信股份 安奈尔 永悦科技 大理药业 &#xff0c;高新发展 也补跌了&#xff0c;收尸队也干活了&#xff0c;情绪不修复不接力得最好写照。这轮周期 宁科生物 已经7板&#xff0c;已经追平了 博信股份7板&#xff0c;看明天溢…

Mac安装minio

Mac安装minio 本文介绍使用 mac 安装 MinIO。 所有软件安装优先参考官网&#xff1a;MinIO Object Storage for MacOS — MinIO Object Storage for MacOS #使用 brew 安装 minio brew install minio/stable/minio#找到 minio tong ~ $ brew list minio /opt/homebrew/Cella…

银行监管报送系统介绍(十二):非居民金融账户涉税信息报送

国家税务总局、财政部、中国人民银行、中国银行业监督管理委员会、中国证券监督管理委员会、国家金融监督管理总局2017年5月9日发布、2017年7月1日起施行的《非居民金融账户涉税信息尽职调查管理办法》。 一、《管理办法》出台的背景是什么&#xff1f;   受二十国集团&…

【unity】如何汉化unity Hub

相信大家下载安装unity后看着满操作栏的英文&#xff0c;英文不好的小伙伴们会一头雾水。但是没关系你要记住你要怎么高速运转的机器进入中国&#xff0c;请记住我给出的原理&#xff0c;不懂不代表不会用啊。现在我们就来把编译器给进行汉化。 第一步&#xff1a;我们打开Uni…

【VTKExamples::Points】第十一期 RadiusOutlierRemoval

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例RadiusOutlierRemoval,并解析接口vtkRadiusOutlierRemoval,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的…

【消息队列开发】 实现 MqClientTests 类——测试客户端

文章目录 &#x1f343;前言&#x1f333;所需属性&#x1f334;BeforeEach&#x1f332;AfterEach&#x1f38d;API测试⭕总结 &#x1f343;前言 本次开发任务 测试客户端接口 &#x1f333;所需属性 所需要一共三个属性 BrokerServer&#xff1a;服务器 ConnectionFa…

考研数学|武忠祥学习包搭配《660》和《880》

一、660、880、三大计算简单分析 660题 这本题册具有高难度、综合度和深度&#xff0c;属于高质量的题材。我建议不要在基础阶段就着手解决其中的660题&#xff0c;因为这可能会影响你的信心。相反&#xff0c;你可以在基础阶段完成一轮学习后&#xff0c;将这些题目留到强化…

安泰ATA-2161高压放大器在生物传感器研究中的应用

生物传感器&#xff08;biosensor&#xff09;&#xff0c;是一种对生物物质敏感并将其浓度转换为电信号进行检测的仪器&#xff0c;它利用固定化生物成分或生物体作为敏感元件。生物传感器并不专指用于生物技术领域的传感器&#xff0c;它的应用领域还包括环境监测、医疗卫生和…

市场复盘总结 20240328

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 40% 最常用的…

MySQL 索引:索引为什么使用 B+树?

Hash 索引不支持顺序和范围查询&#xff1b; 二叉查找树(BST)&#xff1a;解决了排序的问题&#xff0c;极端情况下可能会退化成线性链表&#xff0c;查询效率急剧下降&#xff1b; 平衡二叉树(AVL) &#xff1a;通过旋转解决了平衡的问题&#xff0c;但是旋转操作效率太低&am…

【推导结果】如何得到 回归均方误差 估计系数的标准误

对线性回归模型系数标准差标准误的理解 1.生成数据 yxe3.610.633.42-1.387.631.017.44-1.0111.651.3811.46-0.63 2.回归 y β 0 β 1 x ϵ y \beta_{0}\beta_{1}x\epsilon yβ0​β1​xϵ y i β 0 β 1 x i e i y_{i}\beta_{0}\beta_{1} x_{i}e_{i} yi​β0​β1​xi…