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.mfbz.cn/a/497147.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何压缩视频到最小?教会你压缩原理~

在网上上传视频时&#xff0c;经常会遇到因为视频体积过大上传失败等情况发生&#xff0c;怎么降低视频体积呢&#xff1f;科普一个小知识&#xff1a;视频体积和视频的时长、编码格式、分辨率和比特率&#xff08;又称码率&#xff09;有关。视频文件大小计算公式&#xff1a;…

掼蛋怎么开牌

一、强牌出单张 1、只有打完小单张&#xff0c;才能争得头游。特别是有两三手小牌&#xff0c;必须要先出掉一两手。 2、首发单张&#xff0c;特别是5以下的小单牌&#xff0c;即先打小牌。表明是强牌。尤其是在贡牌后首发小单牌&#xff0c;属于“明知山有虎&#xff0c;偏向…

13.Java能干什么?以及Java的三大平台

文章目录 一、JavaSE二、JavaME三、JavaEE JAVA从95年以来&#xff0c;已经问世了20多年了&#xff0c;可能比部分同学的年龄还大。 Java到底能干嘛呢&#xff0c;此时就需要讲到Java的三大平台&#xff0c;其实也就是它的三个分类&#xff1a;JavaSE、JavaME、JavaEE。 一、Ja…

【Web应用技术基础】CSS(5)——表格样式

第一题&#xff1a;表格边框 .html <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>HTML – 简单表格</title><link rel"stylesheet" href"step1/CSS/style.css"></head><bod…

Git 命令总览

Git Git 是一个版本控制系统&#xff0c;用于管理项目代码。通过 Git 可以轻松地进行代码的提交、更新和合并&#xff0c;确保项目代码的安全性和稳定性。同时&#xff0c;Git 还提供了丰富的工具和功能&#xff0c;如分支管理、代码审查、版本回退等&#xff0c;帮助开发更好…

docker容器内存检测排查

查询容器使用内存 在运维当中&#xff0c;你会发现内存很彪的高&#xff0c;但是如何判断为什么会高&#xff0c;是什么样的程序造成的呢&#xff1f;赶快使用 top&#xff0c;或者 free -h或者 ps -v。是吗&#xff1f;道理是对的。 但是你会发现&#xff0c;全部都是docker…

Java_19 罗马数字转整数

罗马数字转整数 罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1…

如何扫码登记信息?二维码登记信息更加简单快捷

现在很多场所会通过生成二维码的方式来做人员信息登记或者人员签到&#xff0c;那么这种扫描二维码填写信息的制作技巧是什么样的呢&#xff1f;想要做出这种效果&#xff0c;那么需要生成表单二维码&#xff0c;准备好问题和说明&#xff0c;通过专业工具的功能就可以轻松完成…

Redis中的LRU算法分析

LRU算法 概述 Redis作为缓存使用时&#xff0c;一些场景下要考虑内容的空间消耗问题。Redis会删除过期键以释放空间&#xff0c;过期键的删除策略 有两种: 1.惰性删除:每次从键空间中获取键时&#xff0c;都检查取得的键是否过期&#xff0c;如果过期的话&#xff0c;就删除…

轻松上手,小白也能免费部署自己的炫酷静态网站!(如何免费搭建个人网站)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 使用方法 📒📝 操作步骤📝 更换主题⚓️ 相关链接 ⚓️📖 介绍 📖 在互联网时代,拥有一个个人网站无疑是展示自我、分享知识或建立品牌形象的有效途径。然而,许多人在追求这一目标的过程中,常常因为成本、时间和技…

【C++】1320. 时钟旋转(1)

问题 类型&#xff1a;整数运算 题目描述&#xff1a; 时钟上面的时针从 m 时走到 n 时旋转了多少度&#xff1f;&#xff08;m≤n&#xff0c;且 m 和 n 都是1∼12之间的整数&#xff09;。 输入&#xff1a; 2 个整数 m 和 n。 输出&#xff1a; 1 个整数代表时针旋转的…

[数据结构]插入和希尔排序

一、插入排序 插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴&#xff0c;但它的原理应该是最容易理解的了&#xff0c;因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法&#xff0c;它的工作原理是通过构建有序序列&#xff0c;对于未排…

全球金融市场的汇率与政策利率演变:历史与未来的交汇

根据国际货币基金组织&#xff08;IMF&#xff09;等平台的数据&#xff0c;整理了全球各国的兑美元汇率&#xff0c;短期利率、长期利率、政策利率数据&#xff0c;时间范围最新至2024年3月&#xff0c;希望对大家有所帮助 一、数据介绍 数据名称&#xff1a;全球各国汇率、短…

O2OA(翱途)开发平台-快速入门开发一个门户实例

O2OA(翱途)开发平台[下称O2OA开发平台或者O2OA]拥有门户页面定制与集成的能力&#xff0c;平台通过门户定制&#xff0c;可以根据企业的文化&#xff0c;业务需要设计符合企业需要的统一信息门户&#xff0c;系统首页等UI界面。本篇主要介绍通过门户管理系统如何快速的进行一个…

DoubleU-Net:一种用于医学图像分割的深度卷积神经网络

DoubleU-Net&#xff1a;一种用于医学图像分割的深度卷积神经网络 摘要引言相关工作方法 DoubleU-Net A Deep Convolutional Neural Network for Medical Image Segmentation–2020 摘要 语义图像分割是将图像中的每个像素标记为相应的类的过程。基于编码器-解码器的方法&…

如何在Win10使用IIS服务搭建WebDAV网站并实现无公网IP访问内网文件内容

文章目录 前言1. 安装IIS必要WebDav组件2. 客户端测试3. 使用cpolar内网穿透&#xff0c;将WebDav服务暴露在公网3.1 安装cpolar内网穿透3.2 配置WebDav公网访问地址 4. 映射本地盘符访问 前言 在Windows上如何搭建WebDav&#xff0c;并且结合cpolar的内网穿透工具实现在公网访…

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

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

软件设计师24--概念设计阶段

软件设计师24--概念设计阶段 考点1&#xff1a;概念设计过程考点2&#xff1a;E-R图属性E-R模型-联系类型判断例题&#xff1a;E-R模型-联系类型判断扩充的E-R模型 考点1&#xff1a;概念设计过程 需求分析 --> 抽象数据 --> 设计局部ER模型 --> 合并局部模型消除冲突…

接口自动化测试要做什么?8个步骤讲的明明白白

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 先了解下接口测试流程&#xff1a; 1、需求分析 2、Api文档分析与评审 3、测试计划编写 4、用例设…

2024年2月吸尘器行业线上电商(京东天猫淘宝)综合排行榜

鲸参谋监测的线上电商平台&#xff08;淘宝天猫京东&#xff09;2月吸尘器行业销售数据公开。 根据鲸参谋电商数据平台显示&#xff0c;吸尘器行业2月销量累计约53万件&#xff0c;环比上个月下滑29%&#xff0c;同比去年下滑19%&#xff1b;销售额累计约4亿&#xff0c;环比上…