接下来的实验中,会写一个把 GPIO 当作是中断的来源的程序。这个 GPIO 由 Arduino 发出,每当边缘上升时,忌讳触发一次 IRQ。
这个应用比如说 DHT11 在核心的驱动程序,就是用这种机制来实作从 DHT11 收到的讯号:每次发生 edge-triggered 时,都把资料纪录推进 buffer 最後方,最後再解析整个 buffer 的内容,去判断读取的数值是多少。
在这之前,可能会想先知道一下关於目前的 IRQ 相关的资讯与统计资料。这可以透过察看 /proc/interrupts
这个内容得知:
$ cat /proc/interrupts
会出现类似以下的输出:
这个档案中会显示每个 CPU 处理中断的次数。接下来会比较载入模组前後不同的地方。为了方便,等一下在比较的时候,会省略掉中间 CPU 的执行次数,只留下第一栏的 IRQ 编号,以及最後 4 拦的说明。
Raspberry Pi 的 GPIO17
透过 Logic Level Shifter 连接给 Arduino 的 A0
,并且在 Logic Level Shifter 的两端都加上适当的供电。如下图:
在程序能执行之前,当然少不了装置树的准备。不过这个跟前面大同小异,所以就把内容放在附录。
这边资料结构的设计,就是把 irq
编号跟对应的 gpio descriptor 形成一个结构体:
struct minirq_dev {
struct gpio_desc *gpiod;
struct work_struct work;
int irq;
};
就是在 probe
当中,把对应的资源,比如说记忆体空间与 GPIO 等等进行初始化:
static int minirq_probe(struct platform_device *pdev)
{
struct device *dev = &(pdev-> dev);
struct minirq_dev *minirq;
int ret = 0;
minirq = devm_kzalloc(dev, sizeof(struct minirq_dev), GFP_KERNEL);
minirq->gpiod = devm_gpiod_get_index(dev, "minirq", 0, GPIOD_IN);
...
}
为了清楚,错误处理的程序没有列出来。详细的程序会於最後面附上。
参考 GPIO 文件的 GPIOs mapped to IRQs:
static int minirq_probe(struct platform_device *pdev)
{
...
minirq->irq = gpiod_to_irq(minirq->gpiod);
...
}
这边有个前提是:GPIO 的 controller 要可以作为中断的来源,才可以这样做。关於这点可以去看装置树:
$ dtc -I fs /proc/device-tree | less
就会在 GPIO 的部分,发现 interrupts 相关的属性:
gpio@7e200000 {
compatible = "brcm,bcm2835-gpio";
gpio-controller;
#interrupt-cells = < 0x02 >;
interrupts = < 0x02 0x11 0x02 0x12 >;
phandle = < 0x10 >;
reg = < 0x7e200000 0xb4 >;
#gpio-cells = < 0x02 >;
pinctrl-names = "default";
interrupt-controller;
...
};
上半部要实作一个 prototype 为:
irqreturn_t (*irq_handler_t)(int, void *);
的函数。其中,第一个参数是刚刚得到的 irq
编号,而第二个参数是一个「能用以辨认装置的唯一结构」。不过通常都是把类似 struct platform_device
,或是 struct device
这类的资料结构传进去:
static irqreturn_t irq_top_half(int irq, void *p)
{
struct platform_device *pdev = p;
struct minirq_dev *minirq;
minirq = platform_get_drvdata(pdev);
schedule_work(&(minirq -> work));
return IRQ_HANDLED;
}
在这个 top-half 中,做的事情就是把一个工作用 schedule_work
推给一个核心全域的 Workqueue 做,然後就结束。而这个回传的值必须是个 irqreturn_t
,相关的定义可以在这里找到:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
其中,如果这个 IRQ 是现在这个执行单元需要负责处理的,那麽处理完之後就回传 IRQ_HANDLED
。而那个 IRQ_NONE
会出现的原因是:不同的执行单元有可能会同时帮一个 IRQ 注册各自的 IRQ handler。这时如果有 IRQ,那麽这所有的 handler 就会同时被触发。
如果这种共享的状况可能发生,那麽在 handler 里面就要判断被触发时,是不是当下的执行单元需要去理会的?如果发现不是,就什麽都不做,直接回传 IRQ_NONE
就好; 反之,如果是的话,就去把它处理掉,最後再回传 IRQ_HANDLED
。
而如同昨天描述的,上半部是 interrupt context,所以不能在里面休眠。而且处理要快,所以这边就把工作交给 Workqueue 去处理,然後就速速离开。这个下半部也可以比如说是 tasklet 或 kthread,或是整个用 threaded IRQ 来处理。但总之这边使用 Workqueue。
至於要把什麽样的工作推给 Workqueue 呢?这边的 struct work_struct
是嵌入在刚刚的 minirq
中的那个成员。我们就把他在 probe
里面时初始化成下面这个东西:
static int minirq_probe(struct platform_device *pdev)
{
...
INIT_WORK(&minirq->work, irq_bottom_half);
...
}
其中,irq_bottom_half
是一个下面这样的函数:
void irq_bottom_half(struct work_struct *work)
{
pr_info("Rising edge detected!\n");
return;
}
没错,他就是简单印出一个资料,让我们知道 IRQ 被触发了:
上半部跟下半部都处理好之後,接着就在 probe
当中帮这个 IRQ 「注册」这个 IRQ handler:
static int minirq_probe(struct platform_device *pdev)
{
...
ret = devm_request_irq(dev, minirq->irq, irq_top_half,
IRQF_TRIGGER_RISING, "minirq", pdev);
...
}
首先,这个函式有几种变形。最一开始的是 request_irq()
:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags, const char * name, void * dev)
其中,irq
是 IRQ 编号; handler
就是前面所说的,上半部的函式:
irqreturn_t (*irq_handler_t)(int irq, void *p);
而 flag
则是这个 IRQ 的细部调整,比如说是上升触发还是下降触发?有没有跟其他装置共享?是不是 timer 触发的?等等。详细的叙述可以在include/linux/interrupt.h 中找到。最後,那个 dev
会在参数中的 handler
被呼叫时,作为他的二个参数传给 handler
。
而如果使用 request_irq
,那麽事後就要有对应的 free_irq
。而类似地,虽然文件中没有写到,但这个函数有 devm_*
版本的函式,会自动跟装置有关的资源 (也就是这里使用的),因此就不用担心清理的问题。
最後一个版本是 threaded IRQ,虽然说用法很类似 (事实上是更方便),但本质上跟现在这个 IRQ 不同。这个之後会另外介绍。
大致上就是装置树的配置、提供 of_device_id
、模组的初始化、提供 platform_driver
的资料结构等等。为版面简洁,这边就不多赘述。完整程序码附於後方。
每 0.5 秒改变一次电位高低:
void setup() {
pinMode(A0, OUTPUT);
Serial.begin(9600);
}
void loop() {
digitalWrite(A0, HIGH);
delay(500);
digitalWrite(A0, LOW);
delay(500);
}
换句话说,每 1 秒会有一个上升的边缘。
载入模组之後,再去用 /proc/interrupts
观察,既可以发现 pinctrl-brcm2835
後面出现了一个 minirq
的装置:
161: bcm2836-timer 0 Edge arch_timer
162: bcm2836-timer 1 Edge arch_timer
165: bcm2836-pmu 9 Edge arm-pmu
-167: pinctrl-bcm2835 17 Edge
+167: pinctrl-bcm2835 17 Edge minirq
FIQ: usb_fiq
IPI0: CPU wakeup interrupts
IPI1: Timer broadcast interrupts
除此之外,用 dmesg
也可以看见每秒一次印出的讯息:
[ 4222.224497] Rising edge detected!
[ 4223.225439] Rising edge detected!
[ 4224.226387] Rising edge detected!
[ 4225.227340] Rising edge detected!
[ 4226.228285] Rising edge detected!
[ 4227.229231] Rising edge detected!
[ 4228.230173] Rising edge detected!
[ 4229.231120] Rising edge detected!
[ 4230.232070] Rising edge detected!
[ 4231.233014] Rising edge detected!
[ 4232.233958] Rising edge detected!
装置树的部分跟 IIO 时类似,只是名称有所不同:
/dts-v1/;
/plugin/;
/ {
compatible="brcm,brcm2835";
fragment@0 {
target = <&gpio>;
__overlay__ {
minirq: minirq_gpio_pins {
brcm,pins = <0x11>;
brcm,function = <0x0>;
brcm,pull = <0x1>;
};
};
};
fragment@1 {
target-path = "/";
__overlay__ {
minirq {
minirq-gpios = <&gpio 0x11 0x0>;
compatible = "minirq";
status = "ok";
pinctrl-0 = <&minirq>;
pinctrl-names = "default";
};
};
};
};
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
struct minirq_dev {
struct gpio_desc *gpiod;
struct work_struct work;
int irq;
};
void irq_bottom_half(struct work_struct *work)
{
pr_info("Rising edge detected!\n");
return;
}
static irqreturn_t irq_top_half(int irq, void *p)
{
struct platform_device *pdev = p;
struct minirq_dev *minirq;
minirq = platform_get_drvdata(pdev);
schedule_work(&(minirq -> work));
return IRQ_HANDLED;
}
static int minirq_probe(struct platform_device *pdev)
{
struct device *dev = &(pdev-> dev);
struct minirq_dev *minirq;
int ret = 0;
minirq = devm_kzalloc(dev, sizeof(struct minirq_dev), GFP_KERNEL);
if (!minirq) {
dev_err(dev, "Failed to allocate memory.\n");
return -ENOMEM;
}
minirq->gpiod = devm_gpiod_get_index(dev, "minirq", 0, GPIOD_IN);
if (IS_ERR(minirq->gpiod)) {
dev_err(dev, "Failed to get gpio descriptor.\n");
return PTR_ERR(minirq -> gpiod);
}
ret = gpiod_to_irq(minirq->gpiod);
if (ret < 0) {
dev_err(dev, "Failed to get irq from gpiod.\n");
return ret;
}
minirq->irq = ret;
INIT_WORK(&minirq->work, irq_bottom_half);
ret = devm_request_irq(dev, minirq->irq, irq_top_half,
IRQF_TRIGGER_RISING, "minirq", pdev);
if (ret < 0) {
dev_err(dev, "Failed to request IRQ.\n");
return ret;
}
platform_set_drvdata(pdev, minirq);
return 0;
}
static const struct of_device_id minirq_ids[] = {
{.compatible = "minirq",},
{}
};
static struct platform_driver minirq_driver = {
.driver = {
.name = "minirq",
.of_match_table = minirq_ids,
},
.probe = minirq_probe
};
MODULE_LICENSE("GPL");
module_platform_driver(minirq_driver);
PWD := $(shell pwd)
KVERSION := $(shell uname -r)
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
MODULE_NAME = minirq
obj-m := $(MODULE_NAME).o
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) clean
rm -f $(MODULE_NAME).dtbo
dts:
dtc -@ -I dts -O dtb -o $(MODULE_NAME).dtbo $(MODULE_NAME).dts
顺带一提,可以直接:
$ make dts
来编译 .dtbo
。
<<: [Day 28] - Gatsby feat. EC ( 下 )
>>: 这些日子我学到的JavaScript:Day25- to-do list 练习
在写好flask 服务之後,可能会将服务给弱点分析软件进行扫描, 之後会显示出一些高风险的漏洞, 而...
今天要介绍的装饰器模式,跟之前提到过的转接器模式有点类似(但其实结果完全不一样)。 转接器模式的功能...
#前言 由於前一篇使用了const与extern,但对这两者还不太了解,於是又去看了其他人的文章,试...
#993 - Cousins in Binary Tree 连结: https://leetcod...
Vue资料的使用方式 在前一篇中,我们已经会一些用来表示内容的方式了,但仅仅只是呈现~ 所以今天就会...