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.hqwc.cn/news/577609.html

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

相关文章

基于单片机智能家居控制系统设计

**单片机设计介绍&#xff0c;基于单片机智能家居控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能家居控制系统设计旨在实现家居设备的自动化控制和智能化管理&#xff0c;提高家庭生活的便利性和舒…

6、父子组件传参、路由的嵌套、命名视图、路由跳转传参

一、父子组件传参 1、父传子 在父组件的子组件中自定义一个属性在子组件中有一个props属性&#xff0c;用来接收父组件传递的数据,传递的数据不能修改,还可以设置默认值 <!-- 父组件 -->data() {return {flag: false,num:10, //传的参数free:}} <!-- :type1"…

如何准备科学海报

科学会议上的海报展示可以为早期职业研究人员提供宝贵的机会来练习他们的沟通技巧&#xff0c;获得有关他们研究的反馈&#xff0c;并扩大他们的网络。“通过与其他研究人员一对一地讨论我的工作&#xff0c;[我发现]我可以确定哪些工作做得好&#xff0c;哪些需要改进&#xf…

打PTA (15分)(JAVA)

目录 题目描述 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 题解 题目描述 传说这是集美大学的学生对话。本题要求你做一个简单的自动问答机&#xff0c;对任何一个问句&#xff0c;只要其中包含 PTA 就回答 Yes!&#xff0c;其…

IDEA超强攻略,实用插件、进阶AI插件、实用配置

文章目录 IDEA超强攻略实用插件Alibaba Java Coding GuidelinesCheckStyleSonarLintEasyCode 进阶AI插件TabnineCodeGeeX通义灵码&#xff08;TONGYI Lingma&#xff09;Bito 实用配置设置字体大小设置主题风格自动导包&删除无用包配置类注释模板配置方法注释模板打开IDEA不…

速成软件书是神器还是焦虑?

一、背景 "速成软件书"通常是指那些宣称能帮助读者在短时间内掌握某种软件操作或编程技能的书籍。这类书籍往往以其高效、快捷的学习路径吸引读者&#xff0c;尤其适合有一定基础或者急需短期内提升特定技能的人群。 然而&#xff0c;“神器”之称则带有主观性和一…

Linux 环境安装Nginx—源码和Dokcer两种安装方式

一、源代码编译安装Nginx 1.下载最新nginx源码 以nginx-1.25.3.tar.gz为例&#xff1a; 可以使用命令(联网)&#xff1a;curl -O http://nginx.org/download/nginx-1.25.3.tar.gz或在官网下载.tar.gz 2.解压缩 tar -zxvf nginx-1.25.3.tar.gz cd nginx-1.25.3/ 3.安装依赖…

【MySQL】DQL-基础查询-语句&演示(查询多个字段 / 所有字段/并设置别名/去重)

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

P3369 【模板】普通平衡树(splay 算法)

题目描述 您需要写一种数据结构&#xff08;可参考题目标题&#xff09;&#xff0c;来维护一些数&#xff0c;其中需要提供以下操作&#xff1a; 插入一个数 x。删除一个数 x&#xff08;若有多个相同的数&#xff0c;应只删除一个&#xff09;。定义排名为比当前数小的数的…

【MySQL】DML的表操作详解:添加数据&修改数据&删除数据(可cv例题语句)

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

Fastgpt 无法启动或启动后无法正常使用的讨论(启动失败、用户未注册等问题这里)

FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景&#xff01; FastGPT是非常实用并且相当厉害的个人知识库AI项目&#xff0c;项目是非常…

python练习四

1. 求一个十进制的数值的二进制的0、1的个数 def count_binary_ones(n):binary_str bin(n)[2:] # 转换为二进制字符串&#xff0c;去除前缀0bprint(f"{n} 的二进制为: {binary_str}")return binary_str.count(0), binary_str.count(1) n int(input("输入一个…