Linux驱动开发:中断子系统

目录

1、 设备树详解

2、中断相关函数API

2.1 of_find_node_by_path

2.2 irq_of_parse_and_map:根据节点获取中断号

2.3 gpio_to_irq:根据gpio节点获得中断号

2.4 request_irq

2.4 free_irq

3、中断顶半部

3.1 无platform总线

3.1.1 修改设备树

3.1.2 驱动程序

3.2 platform总线下

3.2.1 修改设备树:添加pinctrl

3.2.2 修改设备树:添加按键节点

3.2.3 驱动程序

4、中断底半部

4.1 为什么需要底半部,有哪些方法

4.2 tasklet

4.2.1 tasklet函数API

4.2.2 struct tasklet_struct

4.2.3 tasklet_init :旧版本

4.2.4 tasklet_setup :新版本

4.2.5 tasklet_schedule

4.2.6 tasklet驱动程序

4.3 工作队列

4.3.1 工作队列函数API

4.3.2 work_struct

4.3.3 INIT_WORK

4.3.4 schedule_work

4.3.5 工作队列驱动程序

5、程序中一些函数解析


1、 设备树详解

参考文档

Documentation\devicetree\bindings\interrupt-controller\interrupts.txt

内核目录

Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dts

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

interrupt-parent=<&XXXX>
你要用哪一个中断控制器里的中断?

interrupts
你要用哪一个中断?

Interrupts里要用几个cell,由 interrupt-parent 对应的中断控制器决定。在中断控制器里有“#interrupt-cells”属性,它指明了要用几个cell来描述中断。

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

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

在imx6ull.dtsi中我们可以找到以下中断的节点:

intc: interrupt-controller@00a01000 {
    compatible = "arm,cortex-a7-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x00a01000 0x1000>,
          <0x00a02000 0x100>;
};

在其中我们可以看到 interrupt-cells 是3,第一个cell代表是中断的类型,有三种,分别是:

SGI(Software Generated Interrupts)软件中断

PPI(Private Peripheral Interrupts)私有外设中断

SPI(Shared Peripheral Interrupts)共享外设中断 

 第二个cell代表的是此类型的中断的哪一个中断,第三个代表触发类型。

我们使用的是按键中断,GPIO也可以看成是一个中断控制器,我们先找到GPIO1的节点

gpio1: gpio@0209c000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x0209c000 0x4000>;
    interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
                       <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

在此节点中,并没有标注他的父节点是什么,所以我们去看他的父节点的父节点是什么,因为子节点的一些信息是继承的父节点,他的父节点是soc节点:

soc {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "simple-bus";
    interrupt-parent = <&gpc>;
    ranges;
}

在其中我们可以看到实际上gpio1的父节点是gpc:

gpc: gpc@020dc000 {
    compatible = "fsl,imx6ul-gpc", "fsl,imx6q-gpc";
    reg = <0x020dc000 0x4000>;
    interrupt-controller;
    #interrupt-cells = <3>;
    interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-parent = <&intc>;
    fsl,mf-mix-wakeup-irq = <0xfc00000 0x7d00 0x0 0x1400640>;
};

如果我们要使用gpio1的中断,需要用两个 cell 来修饰,因为 gpio1 中的 gpio-cells 是2

#interrupt-cells=<1>
别的节点要使用这个中断控制器时,只需要一个cell 来表明使用“哪一个


#interrupt-cells=<2>
别的节点要使用这个中断控制器时,需要一个cell来表明使用“哪一个中断”;
还需要另一个cell来描述中断,一般是表明触发类型:

第2个cell的bits[3:0] 用来表示中断触发类型(trigger type and level flags):

1 = low-to-high edge triggered,上升沿触发

2 = high-to-low edge triggered,下降沿触发

4 = active high level-sensitive,高电平触发

8 = active low level-sensitive,低电平触发

2、中断相关函数API

2.1 of_find_node_by_path

struct device_node *of_find_node_by_path(const char *path)
/*
功能:通过路径或者节点结构体
参数:
    @path:路径
返回值:成功返回节点的首地址,失败返回NULL
*/

2.2 irq_of_parse_and_map:根据节点获取中断号

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
/*
功能:解析得到软中断号
参数:
    @dev:节点指针
    @index:索引号
返回值:成功返回大于0的值,失败返回0
*/

2.3 gpio_to_irq:根据gpio节点获得中断号

int gpio_to_irq(unsigned int gpio)
/*
功能:获取GPIO 对应的中断号
参数:
    @gpio:要获取的GPIO编号
返回值:成功返回GPIO对应的中断号
*/

2.4 request_irq

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev)
/*
功能:注册中断
参数:
    @irq:软中断号
    @handler:中断处理函数的函数指针
        //中断处理函数
         irqreturn_t key_irq_handle(int irq, void *dev)
         {
        		return IRQ_NONE	;	//中断没有处理
				return IRQ_HANDLED;	//中断处理完成了
    	}
	@flags:中断触发方式
        	IRQF_TRIGGER_RISING	 
            IRQF_TRIGGER_FALLING 
            IRQF_TRIGGER_HIG 
            IRQF_TRIGGER_LOW
        	IRQF_SHARED  //共享中断
	@name:中断的名字   cat /proc/interrupts
	@dev:向中断处理函数传递的参数
返回值:成功返回0,失败返回错误码
*/

2.4 free_irq

void *free_irq(unsigned int irq, void *dev)
/*
功能:释放中断
参数:
     @irq:软中断号
	 @dev:向中断处理函数传递的参数         
返回值:返回request_irq的第4个参数name
*/

3、中断顶半部

3.1 无platform总线

3.1.1 修改设备树

//在根节点外添加此节点
myirqs{
	interrupt-parent = <&gpiof> ;
	interrupts = <9 0>,<7 0>,<8 0>;
};

3.1.2 驱动程序

#define IRQNAME "key_irq"
struct device_node* node;
unsigned int irqno[3];
char *name[] = {"key_irq1","key_irq2","key_irq3"};
irqreturn_t key_irq_handle(int irq, void* dev)
{
    switch((int)dev){
        case 0:
            printk("key1\n");
            break;
        case 1:
            printk("key2\n");
            break;
        case 2:
            printk("key3\n");
            break;
    }       
    return IRQ_HANDLED;
}
static int __init mycdev_init(void)
{
    // 1.获取节点
    node = of_find_node_by_path("/myirqs");
   
    for (i = 0; i < ARRAY_SIZE(irqno); i++) {
        // 2.根据节点获取软中断号
        irqno[i] = irq_of_parse_and_map(node, i);
        
        // 3.注册中断
        request_irq(irqno[i], key_irq_handle, 
            IRQF_TRIGGER_FALLING, name[i], (void *)i);    
        }
    }
    return 0;
}
static void __exit mycdev_exit(void)
{
    for (i = 0; i < ARRAY_SIZE(irqno); i++) {
        free_irq(irqno[i], (void *)i);
    }
}

3.2 platform总线下

3.2.1 修改设备树:添加pinctrl

在 iomuxc 下添加 pinctrl 信息,使用 imx 工具生成

key1_100ask: key1_100ask{ 
    fsl,pins = < MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x000110A0 >; 
};

key2_100ask: key2_100ask{ 
    fsl,pins = < MX6UL_PAD_NAND_CE1_B__GPIO4_IO14 0x000010B0 >; 
};

3.2.2 修改设备树:添加按键节点

在根节点下添加自己的节点

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

3.2.3 驱动程序

struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
} ;

static struct gpio_key *gpio_keys_100ask;

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	val = gpiod_get_value(gpio_key->gpiod);
	
	printk("key %d %d\n", gpio_key->gpio, val);
	
	return IRQ_HANDLED;
}

static int gpio_key_probe(struct platform_device *pdev)
{
	int err, count, i;
	struct device_node *node = pdev->dev.of_node;
	enum of_gpio_flags flag;
	unsigned flags = GPIOF_IN;

	count = of_gpio_count(node);

	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_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
		gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;	
		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;
    
}

static int gpio_key_remove(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	int count, i;

	count = of_gpio_count(node);
	for (i = 0; i < count; i++) {
		free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
	}
	kfree(gpio_keys_100ask);
    return 0;
}


static const struct of_device_id ask100_keys[] = {
    { .compatible = "100ask,gpio_key" },
    { },
};

static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "100ask_gpio_key",
        .of_match_table = ask100_keys,
    },
};

static int __init gpio_key_init(void)
{	
    platform_driver_register(&gpio_keys_driver); 	
	return err;
}

static void __exit gpio_key_exit(void)
{
    platform_driver_unregister(&gpio_keys_driver);
}

4、中断底半部

4.1 为什么需要底半部,有哪些方法

        在有些资料中也将顶半部和底半部称为上半部和下半部,都是一个意思。我们在使用request_irq申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发 ,那么中断处理函数就会执行。

        上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可
以放在上半部完成。
        下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部
去执行,这样中断处理函数就会快进快出。

        中断下半部一般有三种实现,第一种是软终端,但是由于软中断只有32个,所以如果做要用到中断下半部也不会去用到它,把它留给比如说内核工程师来使用。第二种是tasklet,第三种是工作队列,下面会着重第二种第三种来展开。

        tasklet是基于软中断实现的,但是没有个数限制,因为是通过链表实现的。tasklet工作与中断上下文,在底半部处理函数中可以做耗时操作,但是不能做延时或者休眠的操作。tasklet是中断的一个部分,不能够脱离中断执行

        工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。在内核启动的时候默认会启动一个events的线程,这个线程默认处于休眠状态,如果你想让这个线程执行你的代码,只需要将work提交到这个线程维护的队列中。唤醒这个休眠的线程,这个线程就会执行你的驱动的代码。

4.2 tasklet

4.2.1 tasklet函数API

4.2.2 struct tasklet_struct

struct tasklet_struct
{
	struct tasklet_struct *next; //指向下一个成员
	unsigned long state;		 //状态
	atomic_t count; 			 //计数值
	bool use_callback;			 //use_callback 真callback    假func
	union {
		void (*func)(unsigned long data);	//底半部处理函数
		void (*callback)(struct tasklet_struct *t);
	};
	unsigned long data;  //向底半部处理函数传递参数
};

4.2.3 tasklet_init :旧版本

void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)  //旧版本

4.2.4 tasklet_setup :新版本

void tasklet_setup(struct tasklet_struct *t,
		   void (*callback)(struct tasklet_struct *))   //新版本

4.2.5 tasklet_schedule

void tasklet_schedule(struct tasklet_struct *t)

4.2.6 tasklet驱动程序

#define IRQNAME "key_irq"
struct device_node *node;
//初始化对象
struct tasklet_struct mytask;
unsigned int irqno;

void tasklet_callback(unsigned long) 
{
	//tasklet函数,在这里可以执行一些比较耗时的操作
}

//tasklet_schedule调度tasklet时,其中的函数并不会立刻执行,而只是把tasklet放入队列
irqreturn_t key_irq_handle(int irq, void *dev)
{
    //调度 tasklet
	tasklet_schedule(&mytask);
	return IRQ_HANDLED;
}

static int __init mycdev_init(void)
{
    //初始化 tasklet
	tasklet_setup(&mytask, tasklet_callback);
	
	//1、获取节点
	node = of_find_node_by_path("/myirqs");

	//2、根据节点获取软中断号
	irqno = irq_of_parse_and_map(node, 0);

	//3、注册中断
	ret = request_irq(irqno, key_irq_handle, IRQF_TRIGGER_FALLING, IRQNAME, NULL);

	return 0;
}

static void __exit mycdev_exit(void) 
{
	free_irq(irqno, NULL);
}

4.3 工作队列

4.3.1 工作队列函数API

4.3.2 work_struct

struct work_struct {
    atomic_long_t data;  //携带的数据
    struct list_head entry; //构成队列
    work_func_t func;  //底半部处理函数
};
typedef void (*work_func_t)(struct work_struct *work);

4.3.3 INIT_WORK

#define INIT_WORK(_work, _func)

4.3.4 schedule_work

bool schedule_work(struct work_struct *work)

4.3.5 工作队列驱动程序

#define IRQNAME "key_irq"
struct device_node  *node;
struct work_struct work;
unsigned int irqno;

void work_func(struct work_struct* work) 
{

}

irqreturn_t key_irq_handle(int irq, void *dev)
{
	schedule_work(&work);
	return IRQ_HANDLED;
}

static int __init mycdev_init(void)
{
	//初始化工作队列
	INIT_WORK(&work, work_func);
	
	//1、获取节点
	node = of_find_node_by_path("/myirqs");

	//2、根据节点获取软中断号
	irqno = irq_of_parse_and_map(node, 0);

	//3、注册中断
	request_irq(irqno, key_irq_handle, IRQF_TRIGGER_FALLING, IRQNAME, NULL);

	return 0;
}

static void __exit mycdev_exit(void) 
{
	free_irq(irqno, NULL);
}

5、程序中一些函数解析

在3.3.2中有一个 of_gpio_count ,他得到为什么是 gpios 属性的个数呢

static inline int of_gpio_count(struct device_node *np)
{
	return of_gpio_named_count(np, "gpios");
}

static inline int of_gpio_named_count(struct device_node *np, const char* propname)
{
	return of_count_phandle_with_args(np, propname, "#gpio-cells");
}

int of_count_phandle_with_args(const struct device_node *np, const char *list_name,
				const char *cells_name)
{
    of_phandle_iterator_init(&it, np, list_name, cells_name, 0);
}

int of_phandle_iterator_init(struct of_phandle_iterator *it,
		const struct device_node *np,
		const char *list_name,
		const char *cells_name,
		int cell_count)
{
    list = of_get_property(np, list_name, &size);
}

//简单来看就是
of_get_property(np, "gpios", &size);
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值