[TOC]
These are Linux kernel modules demonstrating different IRQ mechanisms in Linux kernel 5.4. Each of them can be triggered by rising edge on GPIO17
of a Raspberry 4B. In particuler:
-
Examples for dt-binding and basic
[devm_]request_irq()
function usage:A0-interrupt-dt
: devicetree binding for an interrupt generating device.A1-request-irq
: getting IRQ number from devicetree and registering a handler.
-
Examples of using generic work-deferring mechanisms as bottom-half of an IRQ:
B0-workqueue
: example ofworkqueue
as bottom-half of an IRQ.B1-tasklet
: example oftasklet
as bottom-half of an IRQ.
-
Example of threaded IRQ:
C0-threaded-irq
See README
in each directory for detail.
These modules work on Raspberry Pi 4B with 64-bit Ubuntu 20.04 Server, which ships with 5.4 kernel. Official installation guide can be found on Ubuntu website. Later version of kernel should work fine, but tracing results my differ.
Also, everything should run on Raspberry Pi OS (formerly Raspbian) as well, except the way to apply devicetree overlay. See Using Device Trees on Raspberry Pi section on Raspberry Pi OS documentation.
Install dependencies for building kernel modules:
$ sudo apt install make device-tree-compiler linux-headers-$(uname -r)
If you want to trace IRQ handlers with trace-cms
, a ftrace command line front-end:
$ sudo apt install trace-cmd
If you want to trigger IRQ with gpioset
instead of set up external hardware, or check gpio status with gpioinfo
:
$ sudo apt install gpiod
If you want to hook tracepoints with bpftrace
:
$ sudo apt install bpftrace
Note that bpftrace
may be unavailable on some platforms.
You'll have to apply devicetree overlay in 00-interrupt-dts
before installing any module; otherwise the modules won't probe()
. To do this,
-
Go to
A0-interrupt-dt
directory andmake
:$ cd A0-interrupt-dt $ make
There will be a
arduino-irq.dtbo
file to be overlayed:$ ls Makefile arduino-irq.dtbo arduino-irq.dts
-
Copy
.dtbo
file to/boot/firmware/overlays/
directory:$ sudo cp arduino-irq.dtbo /boot/firmware/overlays/
-
Append
dtoverlay=arduino-irq
to/boot/firmware/usercfg.txt
file:# Place "config.txt" changes (dtparam, dtoverlay, disable_overscan, etc.) in # this file. Please refer to the README file for a description of the various # configuration files on the boot partition. dtoverlay=arduino-irq
Modify the file with text editor you prefer, say,
vim
:$ sudo vim /boot/firmware/usercfg.txt
or redirect from
echo
. -
finally reboot:
$ sudo reboot
-
After reboot, verify by looking up devicetree decompiled from
/proc/device-tree/
directory withdtc
:$ dtc -I fs /proc/device-tree/ -O dts | less
If devicetree overlay is applied successfully, node described in
arduino-irq.dts
will show up in decompiled devicetree:arduino { interrupts = <0x11 0x01>; interrupt-parent = <0x0f>; compatible = "arduino,uno-irq"; status = "ok"; phandle = <0xda>; };
You can search text by
/
command inless
The
phandle
property may change under different system configuration, but other properties should be the same.
-
To build module in each directory, simply enter and
make
:$ make
after
make
, there will be aarduino-irq.ko
, which is the kernel module to be installed. -
Kernel modules can be installed by
insmod
:$ sudo insmod arduino-irq.ko
If one of these modules is installed successfully, the following message will show up in
dmesg
[ 441.476796] arduino_irq arduino: successfully probed arduino!
-
To remove a module, simply use
rmmod
command:$ sudo rmmod arduino_irq
You can either trigger this IRQ with gpioset
command, or by setting up external hardware.
To trigger itthis IRQ with gpioset
, simply use:
$ sudo gpioset gpiochip0 17=1
gpioset
will set GPIO line values of a GPIO chip and maintain the state until the process exits. See man gpioset
for detail.
Alternatively, you can trigger this IRQ with external hardware. Any hardware capable of toggling GPIO can be used. Here is an Arduino Uno example:
-
Connect
A0
on Arduino Uno through proper interface (e.g. a logic level shifter) toGPIO17
on Raspberry Pi 4B: -
Connect Arduino Uno to personal computer and upload
trigger.ino
to Arduino. -
You can now fire IRQ manually by sending pressing return to Serial Monitor in Arduino IDE:
Other hardware setup, like a push bottom, is possible. Do note that these modules don't handle debouncing.
trace-cmd
is a command line front-end of ftrace
, the official tracer of Linux kernel. You can look into internals of IRQ in Linux kernel with different tracers in ftrace.
After installing one of these modules, do the following:
-
Run one of the following
trace-cmd
commands:-
To graph the flow of execution of top-half function:
$ sudo trace-cmd record \ -p function_graph \ --max-graph-depth 5 \ -g arduino_irq_top_half \ &
-
To record the flow of execution of both top-half and bottom-half function:
$ sudo trace-cmd record \ -p function_graph \ --max-graph-depth 5 \ -g arduino_irq_top_half \ -g arduino_irq_bottom_half \ &
Note that it only works when a module with bottom-half handler installed.
-
Record top-half function stack:
$ sudo trace-cmd record \ -p function \ --func-stack \ -l arduino_irq_top_half \ &
-
Record both top-half and bottom-half function (if there is one) stack:
$ sudo trace-cmd record \ -p function \ --func-stack \ -l arduino_irq_top_half \ -l arduino_irq_bottom_half \ &
-
-
(Optional) add some work loads. Say
ping
if your RPi is conntected to the internet:$ sudo ping -i 0.001 ubuntu &> /dev/null &
where
-i
option sepcifies the interval between each ping. This should be adjusted according to quiality of network connection. -
Poke it with Arduino, orwith
gpioset
for a few times:$ sudo gpioset gpiochip0 17=1
-
(Optional)
fg
and then Ctrl + C to terminateping
.$ fg ^C
-
fg
and then Ctrl + C to terminatetrace-cmd
. -
Use
-l
option fortrace-cmd
to see report:$ trace-cmd report -l | less
To graph internals of a function in kernel, simply set function_graph
tracer with -g
option, which specifies functions of interest.
In this case, trace-cmd
is set to trace arduino_irq_top_half()
function (by -g
) in background (by &
). The tracer (function_graph
) is set by -p
option, with proper --max-graph-depth
:
$ sudo trace-cmd record \
-p function_graph \
--max-graph-depth 5 \
-g arduino_irq_top_half \
&
For those modules whose bottom-half function is implemented, you can graph both of them at the same time. Just add another -g
option, say:
$ sudo trace-cmd record \
-p function_graph \
--max-graph-depth 5 \
-g arduino_irq_top_half \
-g arduino_irq_bottom_half \
&
After setting up trace-cmd
, poke the IRQ for a few times with Arduino or gpioset
:
$ sudo gpioset gpiochip0 17=1
When done, move trace-cmd
back to foreground by fg
:
$ fg
and then report
with -l
option:
$ trace-cmd report -l | less
If succeeded, you'll see traceing report like this:
ping-2653 0d.h. 870.097107: funcgraph_entry: | arduino_irq_top_half() {
ping-2653 0d.h. 870.097111: funcgraph_entry: | _dev_info() {
ping-2653 0d.h. 870.097112: funcgraph_entry: | __dev_printk() {
ping-2653 0d.h. 870.097113: funcgraph_entry: | dev_printk_emit() {
ping-2653 0d.h. 870.097114: funcgraph_entry: + 17.315 us | dev_vprintk_emit();
ping-2653 0d.h. 870.097133: funcgraph_exit: + 19.370 us | }
ping-2653 0d.h. 870.097133: funcgraph_exit: + 21.203 us | }
ping-2653 0d.h. 870.097134: funcgraph_exit: + 22.870 us | }
ping-2653 0d.h. 870.097135: funcgraph_exit: + 28.556 us | }
irq/43-a-2624 1.... 870.097159: funcgraph_entry: | arduino_irq_bottom_half() {
irq/43-a-2624 1.... 870.097162: funcgraph_entry: | _dev_info() {
irq/43-a-2624 1.... 870.097163: funcgraph_entry: | __dev_printk() {
irq/43-a-2624 1.... 870.097164: funcgraph_entry: | dev_printk_emit() {
irq/43-a-2624 1.... 870.097166: funcgraph_entry: + 12.648 us | dev_vprintk_emit();
irq/43-a-2624 1.... 870.097179: funcgraph_exit: + 14.963 us | }
irq/43-a-2624 1.... 870.097180: funcgraph_exit: + 17.334 us | }
irq/43-a-2624 1.... 870.097181: funcgraph_exit: + 19.574 us | }
irq/43-a-2624 1.... 870.097182: funcgraph_exit: + 23.314 us | }
This is an excert of tracing result from the C0-threaded-irq
. Latency format for these functions reveal context of execution. For example:
arduino_irq_top_half()
(0d.h.
): CPU0 executed it in hard IRQ context (h
), with local irq disabled (d
).arduino_irq_bottom_half()
(1....
): CPU1 ran it in process context (second.
from the right).
Different module uses different IRQ mechanism, so the result depends on the module being installed. Detailed analysis for each case can be found in README of each module.
Alternatively, you can trace function stack with function
tracer. Simply replace the trace-cmd
command from function_graph
tracer to function
tracer and add --function_stack
option, e.g.:
$ sudo trace-cmd record \
-p function \
--func-stack \
-l arduino_irq_top_half \
&
or if any bottom-half function is implemented:
$ sudo trace-cmd record \
-p function \
--func-stack \
-l arduino_irq_top_half \
-l arduino_irq_bottom_half \
&
After setting up trace-cmd
, poke the IRQ for a few time with Arduino or gpioset
:
$ sudo gpioset gpiochip0 17=1
When done, stop trace-cmd
with Ctrl + C after moving trace-cmd
back to foreground by fg
:
$ fg
and then report
with -l
option:
$ trace-cmd report -l | less
If succeeded, you'll see tracing report with repeating format like this:
ping-4211 0..s2 8960.501664: function: arduino_irq_bottom_half
ping-4211 0..s2 8960.501665: kernel_stack: <stack trace>
=> ftrace_graph_call (ffffa31c3f09f7a8)
=> arduino_irq_bottom_half (ffffa31c02ff6240)
=> tasklet_action_common.isra.0 (ffffa31c3f0f8230)
=> tasklet_action (ffffa31c3f0f8348)
=> __do_softirq (ffffa31c3f081cd8)
=> irq_exit (ffffa31c3f0f7ffc)
=> __handle_domain_irq (ffffa31c3f16a2ac)
=> gic_handle_irq (ffffa31c3f08181c)
+=> el1_irq (ffffa31c3f083788)
=> _raw_spin_unlock_irqrestore (ffffa31c3fbbf948)
=> __skb_try_recv_datagram (ffffa31c3f9d7338)
=> __skb_recv_datagram (ffffa31c3f9d746c)
=> skb_recv_datagram (ffffa31c3f9d7510)
=> ping_recvmsg (ffffa31c3fad8624)
=> inet_recvmsg (ffffa31c3fac4d50)
=> sock_recvmsg (ffffa31c3f9bda54)
=> ____sys_recvmsg (ffffa31c3f9bec04)
=> ___sys_recvmsg (ffffa31c3f9beec8)
=> __sys_recvmsg (ffffa31c3f9c3d68)
=> __arm64_sys_recvmsg (ffffa31c3f9c3df0)
=> el0_svc_common.constprop.0 (ffffa31c3f09bd7c)
=> el0_svc_handler (ffffa31c3f09bf48)
+=> el0_svc (ffffa31c3f084690)
This is an excert of report genereted from B1-tasklet
example, showing an IRQ interupting ping
inside a system call it invoked, and arduino_irq_bottom_half()
was going to run inside a tasklet, a category soft IRQ mechanisms, when processor exiting a hard IRQ. See detail in B1-tasklet
module.