目录
2.2 irq_of_parse_and_map:根据节点获取中断号
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);