Linux内核中断子系统

查看中断控制相关的设备树节点

*********************gpiof控制器*************************
    pinctrl: pin-controller@50002000 {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "st,stm32mp157-pinctrl";
            
        interrupt-parent = <&exti>;
                gpiof: gpio@50007000 {
            
            interrupt-controller;//中断标识
            #interrupt-cells = <2>;
    //引用gpiof节点用于中断时,成员需要有两个值            
            status = "disabled";//gpiof为启用
            };
       &pinctrl {
               gpiof: gpio@50007000 {
        status = "okay";
        ngpios = <16>;
        gpio-ranges = <&pinctrl 0 80 16>;
    };

        };
        ************************exti**************************************          
        soc {
        
        #address-cells = <1>;
        #size-cells = <1>;
        interrupt-parent = <&intc>;                           
        exti: interrupt-controller@5000d000 {
            compatible = "st,stm32mp1-exti", "syscon";
            interrupt-controller;
            #interrupt-cells = <2>;
            reg = <0x5000d000 0x400>;
                           };
                           };
  ****************************GIC****************************************
  
    intc: interrupt-controller@a0021000 {
        compatible = "arm,cortex-a7-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0xa0021000 0x1000>,
              <0xa0022000 0x2000>;
    };

编写设备树将按键和中断控制器对接起来

/内核顶层目录/Documentation/devicetree/bindings/interrupt-controller
vi interrupts.txt

 Example:
    interrupt-parent = <&intc1>;//引用中断父节点
    interrupts = <5 0>, <6 0>;//填写的时中断index,第二个成员填0表示默认属性
 
two cells
  ------------
  The #interrupt-cells property is set to 2 and the first cell defines the
  index of the interrupt within the controller, while the second cell is used
  to specify any of the following flags:
    - 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
        
  vi stm32mp157a-fsmp1a.dts
  
    myirq{
    compatible="hqyj,irq";
    interrupt-parent = <&gpiof>;
    interrupts = <9 0>,<7 0>,<8 0>;//节点中引入的中断管脚  pf9 pf8 pf7
    };                                 

按键中断实例

#include <linux/init.h>
#include <linux/module.h>
#include<linux/of.h>
#include<linux/of_gpio.h>
#include<linux/gpio.h>
#include<linux/timer.h>
#include<linux/of_irq.h>
#include<linux/interrupt.h>
/* 
 myirq{
    compatible="hqyj,irq";
    interrupt-parent = <&gpiof>;
    interrupts = <9 0>,<7 0>,<8 0>;
};                 
 };*/
unsigned int irqno[3];
 struct device_node *node;
char *irqname[3]={"key1_int","key2_int","key3_int"};
//中断 处理函数
irqreturn_t irq_handler(int irqno,void *dev)
 {
     //根据request_irq函数的第5个参数来确定是哪一个按键产生的中断
    switch((int)dev)
    {
      case 0:
        printk("key1_int\n");
        break;
      case 1:
        printk("key2_int\n");
        break;
      case 2:
        printk("key3_int\n");
        break;
    }
    return IRQ_HANDLED;
}
 

static int __init mycdev_init(void)
{
 int i,ret;
    //通过名字获取设备树节点信息
    node=of_find_node_by_name(NULL,"myirq");
    if(node==NULL)
    {
        printk("通过路径解析设备树节点信息失败\n");
        return -ENODATA;
    }
      printk("通过路径解析设备树节点信息成功\n");
      //获取软中断号
      //3个按键对应的是3个软中断号,循环注册
  for(i=0;i<ARRAY_SIZE(irqno);i++)
  {
    irqno[i]=irq_of_parse_and_map(node,i);
    if(irqno[i]==0)
    {
      printk("获取软中断号失败 %d\n",i);
      return -EINVAL;
    }
    //注册中断
    ret=request_irq(irqno[i],irq_handler, IRQF_TRIGGER_FALLING,irqname[i],(void *)i);
    if(ret)
    {
      printk("中断注册失败\n");
      return ret;
    }
  }
     
     return 0;
}
static void __exit mycdev_exit(void)
{
  int i;
  //注销中断
  for(i=0;i<ARRAY_SIZE(irqno);i++)
  {
    free_irq(irqno[i],(void *)i);
  }
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

中断底半部

1.什么是中断底半部

在linux内核的中断处理程序里,不允许做延时、耗时甚至休眠的操作,但是有的时候又希望中断到来的时候尽可能多做一些事情,所以两个需求产生了矛盾。linux内核为了解决这个矛盾,引入了中断底半部机制。
中断顶半部:处理紧急的不耗时的任务
中断底半部:处理不紧急的、耗时的操作
中断底半部的实现机制:软中断、tasklet、工作队列

2.软中断

软中断这个底半部实现机制存在个数限制(32),一般留给内核的开发工作者使用

3.tasklet

tasklet是基于软中断来实现,特点和软中断一样,但是没有个数限制(对象通过链表维系)。
tasklet必须基于中断工作,是中断的一部分。
tasklet的底半部处理函数能够做相对耗时的操作,但是不能进行延时或者休眠操作。
tasklet工作于中断上下文。

中断顶半部处理函数执行结束时,将标志位置位,中断标志位清除后底半部机制会判断中断标志位是否置位,如果置位了,就回调底半部处理函数。
对于tasklet来说最多可以同时处理五个底半部事件,此时如果想要再处理多的底半部事件,就需要创建内核线程来进行处理

3.1tasklet相关API 

tasklet对象结构体:
struct tasklet_struct
{
    struct tasklet_struct *next;//tasklet对象链表下一个节点指针
    unsigned long state;//是否执行底半部的状态标志位
    atomic_t count;//底半部触发的次数
    bool use_callback;//false使用func回调函数  true使用callback回调函数
    union {
        void (*func)(unsigned long data);
        void (*callback)(struct tasklet_struct *t);
    };
    unsigned long data;//向底半部传的参数
};

1.分配一个tasklet对象
struct tasklet_struct tasklet;

2.对象初始化
void tasklet_setup(struct tasklet_struct *t,
           void (*callback)(struct tasklet_struct *))
void tasklet_init(struct tasklet_struct *t,
          void (*func)(unsigned long), unsigned long data)

3.启用中断底半部
void tasklet_schedule(struct tasklet_struct *t)

3.2tasklet实例

#include <linux/init.h>
#include <linux/module.h>
#include<linux/of.h>
#include<linux/of_gpio.h>
#include<linux/gpio.h>
#include<linux/timer.h>
#include<linux/of_irq.h>
#include<linux/interrupt.h>
/* 
 myirq{
    compatible="hqyj,irq";
    interrupt-parent = <&gpiof>;
    interrupts = <9 0>,<7 0>,<8 0>;
};                 
 };*/
unsigned int irqno;
 struct device_node *node;
 //1.分配一个tasklet对象
struct tasklet_struct tasklet;


//中断底半部函数
void task_callback(struct tasklet_struct *t)
{
    //进行耗时操作
    int i=50;
    while(--i)
    {
      printk("i=%d\n",i);
    }
}
//中断顶半部处理函数
irqreturn_t irq_handler(int irqno,void *dev)
 {
   //开启中断底半部
   tasklet_schedule(&tasklet);
    return IRQ_HANDLED;
}
 

static int __init mycdev_init(void)
{
      int ret;
      //tasklet对象的初始化
      tasklet_setup(&tasklet,task_callback);
    //通过名字获取设备树节点信息
    node=of_find_node_by_name(NULL,"myirq");
    if(node==NULL)
    {
        printk("通过路径解析设备树节点信息失败\n");
        return -ENODATA;
    }
      printk("通过路径解析设备树节点信息成功\n");
      //获取软中断号

  
    irqno=irq_of_parse_and_map(node,0);
    if(irqno==0)
    {
      printk("获取软中断号失败 %d\n",0);
      return -EINVAL;
    }
    //注册中断
    ret=request_irq(irqno,irq_handler, IRQF_TRIGGER_FALLING,"key1_int",(void *)0);
    if(ret)
    {
      printk("中断注册失败\n");
      return ret;
    }

     
     return 0;
}
static void __exit mycdev_exit(void)
{

  
    free_irq(irqno,(void *)0);
 
 

}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

4.工作队列

工作队列时linux内核启动的时候就默认开启的一个events线程,这个线程默认处于休眠状态。
如果你要是有任务要去执行,只需要将任务提交到工作队列中,唤醒这个线程即可。
工作队列不仅可以用于中断,也可以用于进程上下文。
在底半部处理函数中可以进行延时、耗时甚至休眠的操作。

4.1工作队列相关API

工作队列结构体
struct work_struct {
    atomic_long_t data;//用于进行数据传递的
    struct list_head entry;//工作队列的结构入口
    work_func_t func;//工作队列底半部处理函数指针
    };
1.分配一个工作队列对象
struct work_struct work;
2.初始化对象
typedef void (*work_func_t)(struct work_struct *work);//函数指针
//定义底半部函数
void work_func(struct work_struct *work)
{
    
}
INIT_WORK(&work,work_func);

3.开启底半部处理函数
 bool schedule_work(struct work_struct *work)

4.2工作队列实例

#include <linux/init.h>
#include <linux/module.h>
#include<linux/of.h>
#include<linux/of_gpio.h>
#include<linux/gpio.h>
#include<linux/timer.h>
#include<linux/of_irq.h>
#include<linux/interrupt.h>
/* 
 myirq{
    compatible="hqyj,irq";
    interrupt-parent = <&gpiof>;
    interrupts = <9 0>,<7 0>,<8 0>;
};                 
 };*/
unsigned int irqno;
 struct device_node *node;
 //1.分配一个工作队列对象
struct work_struct work;


//中断底半部函数
void work_func(struct work_struct *work)
{
    int i=50;
    while(--i)
    {
      printk("i=%d\n",i);
    }
}
//中断顶半部处理函数
irqreturn_t irq_handler(int irqno,void *dev)
 {
   //开启中断底半部
    schedule_work(&work);
    return IRQ_HANDLED;
}
 

static int __init mycdev_init(void)
{
      int ret;
      //工作队列对象的初始化
     INIT_WORK(&work,work_func);
    //通过名字获取设备树节点信息
    node=of_find_node_by_name(NULL,"myirq");
    if(node==NULL)
    {
        printk("通过路径解析设备树节点信息失败\n");
        return -ENODATA;
    }
      printk("通过路径解析设备树节点信息成功\n");
      //获取软中断号

  
    irqno=irq_of_parse_and_map(node,0);
    if(irqno==0)
    {
      printk("获取软中断号失败 %d\n",0);
      return -EINVAL;
    }
    //注册中断
    ret=request_irq(irqno,irq_handler, IRQF_TRIGGER_FALLING,"key1_int",(void *)0);
    if(ret)
    {
      printk("中断注册失败\n");
      return ret;
    }

     
     return 0;
}
static void __exit mycdev_exit(void)
{
    free_irq(irqno,(void *)0);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

5、platfrom总线驱动

 5.1什么是总线驱动

linux内核中所有的总线驱动都要遵循总线驱动模型

内核在设计总线驱动模型的时候将一个驱动分为三部分:device、driver、bus。
device是用来描述硬件信息的。
bus是总线用来连接device和driver;driver是用来描述驱动的对象。
在内核中所有的device都是存在内核的klist_device链表中。
内核中所有的driver都是以klist_driver链表中管理。
内核中的device和driver通过bus完成关联,当driver和device通过match函数匹配成功之后,执行驱动里的probe函数,在probe函数中完成硬件的驱动工作

5.2platfrom总线驱动原理

platform总线驱动遵循总线驱动模型,platform是linux内核抽象出来的软件代码,并没有真实的硬件总线协议与之对应。
platform总线驱动的思想是将设备和驱动进行分离。
platform_device和platform_driver通过总线进行匹配成功后执行驱动中的probe函数,在probe函数中获取硬件的设备信息从而操作硬件

 5.3platfrom总线驱动相关API

设备端

#include<linux/platform_device.h>
1.device对象结构体
struct platform_device {
    const char  *name;//用来进行匹配的名字
    int     id;//总线编号  PLATFORM_DEVID_AUTO(自动分配总线号)
    struct device   dev;//父类
    u32     num_resources;//设备信息的个数
    struct resource *resource;//存放设备信息的空间的首地址
};
struct device {
    void    (*release)(struct device *dev);//用于释放device申请的资源,卸载驱动时执行
    };
    
    //资源结构体
    struct resource {
    resource_size_t start;//资源的起始数值 0X50006000       0XC0008000        71(中断号)
    resource_size_t end;//资源的结束数值   0X50006000+4     0XC0008000+49     71
    const char *name;//资源的名字
    unsigned long flags;//资源的类型    IORESOURCE_IO|IORESOURCE_MEM|IORESOURCE_IRQ
};



2.对象的初始化
//填充设备信息
struct resource res[]={
    [0]={
        .start=0x12345678,
        .end=0x12345678+49,
        .flags=IORESOURCE_MEM,  
    },
    [1]={
        .start= 71,
        .end=71,
        .flags=IORESOURCE_IRQ,    
    },
};
//定义一个release函数用于卸载驱动时回收device资源
void    pdev_release(struct device *dev)
{
    
}
//给对象分配空间并且完成对象的初始化
struct platform_device pdev={
    .name="hahahha",
    .id=PLATFORM_DEVID_AUTO,
    .dev={
        .release=pdev_release,    
    },
    .num_resources=ARRAY_SIZE(res),
    .resource=res,
};

3.对象的注册
int platform_device_register(struct platform_device *pdev)
4.对象的注销
void platform_device_unregister(struct platform_device *pdev)

驱动端

1.driver对象结构体
struct platform_driver {
    int (*probe)(struct platform_device *);//匹配成功后执行
    int (*remove)(struct platform_device *);//设备和驱动分离时执行remove
    struct device_driver driver;//父类,用于设置和device端的匹配方式
    const struct platform_device_id *id_table;//设备和device端的匹配方式位idtable
};
//父类结构体
struct device_driver {
    const char      *name;//设置和device端匹配方式位名字匹配
     const struct of_device_id   *of_match_table;//设备树匹配
     };
2.对象的初始化
//probe函数
int pdrv_probe(struct platform_device *pdev)
{
    return 0;
}
//remove函数
 int pdrv_remove(struct platform_device *pdev)
 {
     return 0; 
 }
struct platform_driver pdrv={
    .probe=pdrv_probe,
    .remove=pdrv_remove,
    .driver={
        .name="hahahha",   
    },
};
3.对象的注册
#define platform_driver_register(drv) \
    __platform_driver_register(drv, THIS_MODULE)
4.对象的注销
void platform_driver_unregister(struct platform_driver *drv)

5.4 platfrom总线驱动编程实例

pdev.c

#include <linux/init.h>
#include <linux/module.h>
#include<linux/platform_device.h>

//填充设备信息
struct resource res[]={
    [0]={
        .start=0x12345678,
        .end=0x12345678+49,
        .flags=IORESOURCE_MEM,  
    },
    [1]={
        .start= 71,
        .end=71,
        .flags=IORESOURCE_IRQ,    
    },
};
//定义一个release函数用于卸载驱动时回收device资源
void    pdev_release(struct device *dev)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}

//给对象分配空间并且完成对象的初始化
struct platform_device pdev={
    .name="hahahha",
    .id=PLATFORM_DEVID_AUTO,
    .dev={
        .release=pdev_release,    
    },
    .num_resources=ARRAY_SIZE(res),
    .resource=res,
};

static int __init mycdev_init(void)
{
    //对象的注册
    platform_device_register(&pdev);
    return 0;
}
static void __exit mycdev_exit(void)
{
    //对象的注销
     platform_device_unregister(&pdev);

}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

pdrv.c

#include <linux/init.h>
#include <linux/module.h>
#include<linux/platform_device.h>
//probe函数
int pdrv_probe(struct platform_device *pdev)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    return 0;
}
//remove函数
 int pdrv_remove(struct platform_device *pdev)
 {
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
     return 0; 
 }

 //定义对象并且初始化
 struct platform_driver pdrv={
    .probe=pdrv_probe,
    .remove=pdrv_remove,
    .driver={
        .name="hahahha",   
    },
};

static int __init mycdev_init(void)
{
    //对象的注册
    platform_driver_register(&pdrv);
    return 0;
}
static void __exit mycdev_exit(void)
{
    platform_driver_unregister(&pdrv);

}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

5.5 一键注册宏

#define module_platform_driver(__platform_driver) \
    module_driver(__platform_driver, platform_driver_register, \
            platform_driver_unregister)

#define module_driver(__driver, __register, __unregister, ...) \
    static int __init __driver##_init(void) \
    { \
        return __register(&(__driver) , ##__VA_ARGS__); \
    } \
    module_init(__driver##_init); \
    static void __exit __driver##_exit(void) \
    { \
        __unregister(&(__driver) , ##__VA_ARGS__); \
    } \
    module_exit(__driver##_exit);

5.6 在platfrom驱动中获取设备信息相关API

1.struct resource *platform_get_resource(struct platform_device *dev,
                       unsigned int type, unsigned int num)
功能:在驱动中获取设备信息
参数:
dev:platform_device对象指针
type:资源类型
num:同类型资源的序号,从0开始
返回值:成功返回资源结构体首地址,失败返回NULL

2.int platform_get_irq(struct platform_device *dev, unsigned int num)
功能:获取中断类型的资源
参数:
    dev:platform_device对象指针
    num:同类型资源的序号,从0开始
返回值:成功返回中断号,失败返回错误码

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值