作为驱动工程师,主要的工作就是移植各种驱动,接触各种硬件。接触最多的就是dts、中断、gpio、sysfs、proc fs。如何利用sysfs、proc fs及内核提供的接口为我们降低调试难度
如何利用dts
首先我们关注的主要是两点,gpio和irq。其他的选择忽略。先展示一下我期望的gpio和irq的使用方法。dts如下。
device {
rst-gpio = <&gpioc_ctl 10 OF_GPIO_ACTIVE_LOW>;
irq-gpio = <&gpioc_ctl 11 0>;
interrupts-extended = <&vic 11 IRQF_TRIGGER_RISING>;
};
对于以上的dts你应该再熟悉不过,当然这里不是教你如何使用dts,而是关注gpio和irq最后一个数字可以如何利用。例如rst-gpio的OF_GPIO_ACTIVE_LOW代表什么意思呢?可以理解为低有效。什么意思呢?举个例子,正常情况下,我们需要一个gpio口控制灯,我们认为灯打开就是active状态。对于一个程序员来说,我们可以封装一个函数,写1就是打开灯,写0就是关灯。但是对于硬件来说,变化的是gpio口的电平状态。如果gpio输出高电平灯亮,那么这就是高有效。如果硬件设计是gpio输出低电平灯亮,那么就是低有效。对于一个软件工程师来说,我们的期望是写1就是亮灯,写0就是关灯。我可不管硬件工程师是怎么设计的。我们可以认为dts是描述具体的硬件。因此对于驱动来说,硬件的这种变化,只需要修改dts即可。软件不用任何修改。软件可以如下实现。
int device_probe(struct platform_device *pdev)
{
rst_gpio = of_get_named_gpio_flags(np, "rst-gpio", 0, &flags);
if (flags & OF_GPIO_ACTIVE_LOW) {
struct gpio_desc *desc;
desc = gpio_to_desc(rst_gpio);
set_bit(FLAG_ACTIVE_LOW, &desc->flags);
}
irq = of_irq_get(np, 0);
trigger_type = irq_get_trigger_type(irq);
request_threaded_irq(irq, NULL, irq_handler, trigger_type, "irq", NULL);
}
驱动按照以上代码实现的话,如果修改中断触发类型或者电平有效状态只需要修改dts即可。例如不同的IC复位电平是不一样的,有的IC是高电平复位,有的IC却是低电平复位。其实这就是一个电平有效状态的例子。
如何调试gpio
移植驱动阶段或者调试阶段的工程中,难免想知道当前gpio的电平状态。当然很easy。万用表戳上去不就行了。是啊!硬件工程师的思维。作为软件工程师自然是要软件的方法。下面介绍两个api接口。
debug /sys/kernel/debug/gpio [第一次修改! 需要注意的是.这个状态只对应pin脚的功能是gpio,如果是其他功能.如i2c,spi则无效]
可以看到gpio6,7.在正常状态下是i2c,无内部上下拉,驱动能力是10mA,然而查看/sys/kernel/debug/gpio的时候可以看到里面对应的状态是gpio6,7睡眠状态下的gpio.[第一次修改!]
static inline int gpio_export(unsigned gpio, bool direction_may_change);
static inline int gpio_export_link(struct device *dev, const char *name,
unsigned gpio);
———————
linux提供了一系列的函数来操作GPIO,看下面的代码:
int io_out, in_in; //定义一个输出IO和一个输入IO
io_out = GPIO_TO_PIN(1, 16);
io_in = GPIO_TO_PIN(1, 17);
gpio_request(io_out, “gpio_out”); //申请IO
gpio_request(io_in, “gpio_in”);
gpio_direction_output(io_out, 1); //设置IO为输出,上拉
gpio_direction_input(io_in); //设置IO为输入
gpio_set_value(io_out, 0); //设置IO的输出为低
gpio_get_value(io_in); //读取IO的值
gpio_export(io_out, 1); //导出IO到用户空间
gpio_export(io_in, 0);
———————
用户层也可以将IO配置为中断,即设置/sys/class/gpio/gpioN/edge:
none表示引脚为输入,不是中断引脚
rising表示引脚为中断输入,上升沿触发
falling表示引脚为中断输入,下降沿触发
both表示引脚为中断输入,边沿触发
———————
作者:xgbing
来源:CSDN
原文:https://blog.csdn.net/xgbing/article/details/51009384
如何调试irq
调试的时候确定硬件是否产生中断
- cat /proc/interrupts
根据输出的irq num,进入以下目录。
- cd /proc/irq/irq_num
dts和sysfs有什么关联
曾经写过一篇dts解析的文章
http://www.wowotech.net/device_model/dt-code-file-struct-parse.html
。最后一节其实说了一个很有意思的东西。但是仅仅是寥寥结尾。并没有展开。因为他不关乎我写的文章的的主题。但是,他却和调试息息相关。
- cd /sys/firmware/devicetree/base
该目录的信息就是整个dts。将整个dts的节点以及属性全部展现在sysfs中。他对我们的调试有什么用呢?Let me tell you。如果你的项目非常的复杂,例如一套代码兼容多种硬件配置。这些硬件配置的差异信息主要是依靠dts进行区分。当编译kernel的时候,你发现你很难确定就是你用的是哪个dts。可能你就会凭借自己多年的工作经验去猜测。是的,你的经验很厉害。但是,如何确定你的dts信息是否添加到kernel使用的dts呢?我觉得你应该got it。就到这个目录去查找是否包含你添加的ndoe和property。dts中有status属性可以设置某个devicec是否使能。当你发现你的driver的probe没有执行的时候,我觉得你就需要确定一遍status是不是“ok”、“okay”或者没有这个属性。
在/sys/firmware下也可以看到devicetree的导出信息:
[root@tiny4412 root]# cd /sys/firmware
[root@tiny4412 firmware]# ls -F
devicetree/ fdt
其中fdt是一个二进制文件,其中是完整的设备树镜像,也就是bootloader最终传给kernel的设备树镜像文件,如果是在Andriod系统上,可以用adb pull将该文件导出到开发机上,然后使用dtc对导出的文件进行反编译:
adb pull /sys/firmware/fdt ./fdt
dtc -I dtb -O dts -o fdt.dts ./fdt
linux下如何将dts转换成dtb?
dtc -I dts -O dtb -o <dtb filename> <dts filename>
sysfs可以看出什么
sysfs有什么用?sysfs可以看出device是否注册成功、存在哪些device、driver是否注册、device和driver是都匹配、device匹配的driver是哪个等等。先说第一项技能。deivce是否注册。
就以i2c设备为例说明。/sys/bus/i2c/devices该目录下面全是i2c总线下面的devices。如何确定自己的device是否注册呢?首选你需要确定自己的device挂接的总线是哪个i2c(i2c0, i2c1…)。假设设备挂接i2c3,从地址假设0x55。那么devices目录只需要查看是否有3-0055目录即可。如何确定device是否匹配了驱动?进入3-0055目录,其实你可以看到driver的符号链接。如果没有,那么就是没有driver。driver是否注册如何确定呢?方法类似。/sys/bus/i2c/drivers目录就是所有注册的i2c driver。方法如上。不再列举。
你以为就这些简单的功能了吗?其实不是,还有很多待你探讨。主要是各种目录之间的关系,device注册会出现什么目录?那么driver呢?各种符号链接会在那些目录?等等。
如何排查driver的probe没有执行问题
我想这类问题是移植过程中极容易出现的第一个拦路虎。这里需要声明一点。可能有些驱动工程师认为probe没有执行就是driver没有注册成功。其实这两个没有半毛钱关系。probe是否执行只和device和driver的是否匹配成功有关。我们有什么思路排查这类问题呢?主要排查方法可以如下。
1. 通过sysfs确定对应的device是否注册成功。
2. 通过sysfs确定对应的driver是否注册成功。
3. 通过sysfs中dts的展开信息得到compatible属性的值,然后和driver的compatible进行对比。
如果发现device没有注册,如何确定问题。首先通过sysfs中dts展开文件查看是否有你添加的device信息。如果没有的话,没有device注册就是正常的,去找到正确的dts添加正确的device信息。如果发现sysfs有device的dts信息。那么就看看status属性的值是不是ok的。如果也是ok的。那么就非常奇怪了。这种情况一般出现在kernel的device注册路径出错了。曾经就有一个小伙伴提出问题,现象就是这样。最后我帮他确定问题原因是dts章reg属性的地址是0xc0。这是一个大于0x7f的值。在i2c总线注册驱动的时候会解析当前总线下的所有device。然后注册所有的从设备。就是这里的地址检查出了问题,因此这个device没有注册上。
如果发现driver没有注册,那么去看看对应的Makefile是否参与编译。如果参与了编译,就查看log信息,是不是驱动注册的时候有错误信息。
最后一点的compatible属性的值只需要cat一下,然后compare即可。不多说。
https://blog.csdn.net/xgbing/article/details/51009384
http://www.wowotech.net/device_model/429.html
https://blog.csdn.net/feifei_csdn/article/details/80804804
https://www.cnblogs.com/pengdonglin137/p/5248114.html
https://www.kernel.org/doc/html/v4.11/admin-guide/dynamic-debug-howto
Dynamic debug
Introduction[对应kernel4.14及以上]
本文档描述了如何使用动态调试(dyndbg)功能.
动态调试的目的是允许您动态地启用/禁用内核代码,以获得附加的内核信息。 如果
CONFIG_DYNAMIC_DEBUG
宏默认开启, 所有的
pr_debug()
/
dev_dbg()
,
print_hex_dump_debug()
/
print_hex_dump_bytes()
调用将动态开启输出到日志.
If
CONFIG_DYNAMIC_DEBUG
is not set,
print_hex_dump_debug()
is just shortcut for
print_hex_dump(KERN_DEBUG)
.
For
print_hex_dump_debug()
/
print_hex_dump_bytes()
, format string is its
prefix_str
argument, if it is constant string; or
hexdump
in case
prefix_str
is build dynamically.
动态调试还有更多有用的特性:
- Simple query language allows turning on and off debugging statements by matching any combination of 0 or 1 of:
- source filename
- function name
- line number (including ranges of line numbers)
- module name
- format string
- 提供一个debugfs控制文件:<debugfs>/dynamic_debug/control,可以读取它来显示已知调试语句的完整列表,以帮助指导您
如何使用动态调试手段
pr_debug()
/
dev_dbg()
的行为是通过写入’ debugfs ‘文件系统中的一个控制文件来控制的。因此,您必须首先挂载debugfs文件系统,以便使用这个特性。随后,我们将通过控制文件:
<debugfs>/dynamic_debug/control
.
例如,如果希望启用从源文件svcsock,第1603行打印日志。你只需要:
nullarbor:~ # echo 'file svcsock.c line 1603 +p' >
<debugfs>/dynamic_debug/control
如果你在语法上犯了一个错误,写操作就会失败:
nullarbor:~ # echo 'file svcsock.c wtf 1 +p' >
<debugfs>/dynamic_debug/control
-bash: echo: write error: Invalid argument
查看动态调试输出
您可以通过以下方式查看所有调试语句的当前配置行为:
nullarbor:~ # cat <debugfs>/dynamic_debug/control
# filename:lineno [module]function flags format
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svc_rdma.c:323 [svcxprt_rdma]svc_rdma_cleanup =_ "SVCRDMA Module Removed, deregister RPC RDMA transport\012"
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svc_rdma.c:341 [svcxprt_rdma]svc_rdma_init =_ "\011max_inline : %d\012"
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svc_rdma.c:340 [svcxprt_rdma]svc_rdma_init =_ "\011sq_depth : %d\012"
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svc_rdma.c:338 [svcxprt_rdma]svc_rdma_init =_ "\011max_requests : %d\012"
...
你也可以对这些数据应用标准的Unix文本操作过滤器,例如:
nullarbor:~ # grep -i rdma <debugfs>/dynamic_debug/control | wc -l
62
nullarbor:~ # grep -i tcp <debugfs>/dynamic_debug/control | wc -l
42
第三列显示每个调试语句callsite当前启用的标志(有关标志的定义,请参阅下面)。默认没有启用任何标志值是”=_“。因此,您可以查看所有的调试语句callsite与任何非默认标志:
nullarbor:~ # awk '$3 != "=_"' <debugfs>/dynamic_debug/control
# filename:lineno [module]function flags format
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svcsock.c:1603 [sunrpc]svc_send p "svc_process: st_sendto returned %d\012"
相关命令语句
在词法级别,命令由空格或制表符分隔的单词序列组成。这些都是等价的:
nullarbor:~ # echo -n 'file svcsock.c line 1603 +p' >
<debugfs>/dynamic_debug/control
nullarbor:~ # echo -n ' file svcsock.c line 1603 +p ' >
<debugfs>/dynamic_debug/control
nullarbor:~ # echo -n 'file svcsock.c line 1603 +p' >
<debugfs>/dynamic_debug/control
命令提交由write()系统调用绑定。多个命令可以写在一起,用
;
or
\n
:
~# echo "func pnpacpi_get_resources +p; func pnp_assign_mem +p" \
> <debugfs>/dynamic_debug/control
如果你的查询集很大,你也可以批处理它们:
~# cat query-batch-file > <debugfs>/dynamic_debug/control
另一种方法是使用通配符。匹配规则支持
*
(匹配零个或多个字符) and
?
(只匹配一个字符).例如,你可以匹配所有的usb驱动程序:
~# echo "file drivers/usb/* +p" > <debugfs>/dynamic_debug/control
在语法层面,一个命令包含一系列匹配规范,后面是一个标志更改规范:
command ::= match-spec* flags-spec
The match-spec’s are used to choose a subset of the known pr_debug() callsites to which to apply the flags-spec. Think of them as a query with implicit ANDs between each pair. Note that an empty list of match-specs will select all debug statement callsites.
A match specification comprises a keyword, which controls the attribute of the callsite to be compared, and a value to compare against. Possible keywords are::
match-spec ::= 'func' string |
'file' string |
'module' string |
'format' string |
'line' line-range
line-range ::= lineno |
'-'lineno |
lineno'-' |
lineno'-'lineno
lineno ::= unsigned-int
Note
line-range
不包含空格 e.g. “1-30” 可以,“1 – 30” 不行.
每个关键字的含义:
func
将给定的字符串与每个callsite的函数名进行比较。例子:
func svc_tcp_accept
file
将给定的字符串与完整的路径名、src-root相对路径名或源文件的基本名进行比较 . 比如:
file svcsock.c
file kernel/freezer.c
file /usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svcsock.c
module
将给定的字符串与每个日志输出的模块名进行比较。模块名字等同于使用
lsmod看到的
, 即不带目录或.ko后缀,并将-改为_。例子:
module sunrpc
module nfsd
format
在动态调试格式字符串中搜索给定的字符串。注意,字符串不需要匹配整个格式,只需要匹配部分。 空白和其他特殊字符串可以用双引号字符(“)或单引号字符(‘)括起来。例子:
format svcrdma: // many of the NFS/RDMA server pr_debugs
format readahead // some pr_debugs in the readahead cache
format nfsd:\040SETATTR // one way to match a format with whitespace
format "nfsd: SETATTR" // a neater way to match a format with whitespace
format 'nfsd: SETATTR' // yet another way to match a format with whitespace
line
The given line number or range of line numbers is compared against the line number of each
pr_debug()
callsite. A single line number matches the callsite line number exactly. A range of line numbers matches any callsite between the first and last line number inclusive. An empty first number means the first line in the file, an empty line number means the last number in the file. Examples:
line 1603 // exactly line 1603
line 1600-1605 // the six lines from line 1600 to line 1605
line -1605 // the 1605 lines from line 1 to line 1605
line 1600- // all lines from line 1600 to the end of the file
flags规范包含一个更改操作,后面跟着一个或多个标记字符。更改操作是其中一个字符:
- remove the given flags
+ add the given flags
= set the flags to the given flags
The flags are:
p enables the pr_debug() callsite.
f Include the function name in the printed message
l Include line number in the printed message
m Include module name in the printed message
t Include thread ID in messages not generated from interrupt context
_ No flags are set. (Or'd with others on input)
For
print_hex_dump_debug()
and
print_hex_dump_bytes()
, only
p
flag have meaning, other flags ignored.
For display, the flags are preceded by
=
(mnemonic: what the flags are currently equal to).
Note the regexp
^[-+=][flmpt_]+$
matches a flags specification. To clear all flags at once, use
=_
or
-flmpt
.
启动过程中的调试消息
To activate debug messages for core code and built-in modules during the boot process, even before userspace and debugfs exists, use
dyndbg="QUERY"
,
module.dyndbg="QUERY"
, or
ddebug_query="QUERY"
(
ddebug_query
is obsoleted by
dyndbg
, and deprecated). QUERY follows the syntax described above, but must not exceed 1023 characters. Your bootloader may impose lower limits.
These
dyndbg
params are processed just after the ddebug tables are processed, as part of the arch_initcall. Thus you can enable debug messages in all code run after this arch_initcall via this boot parameter.
On an x86 system for example ACPI enablement is a subsys_initcall and:
dyndbg="file ec.c +p"
will show early Embedded Controller transactions during ACPI setup if your machine (typically a laptop) has an Embedded Controller. PCI (or other devices) initialization also is a hot candidate for using this boot parameter for debugging purposes.
If
foo
module is not built-in,
foo.dyndbg
will still be processed at boot time, without effect, but will be reprocessed when module is loaded later.
dyndbg_query=
and bare
dyndbg=
are only processed at boot.
在模块初始化时调试消息
When
modprobe foo
is called, modprobe scans
/proc/cmdline
for
foo.params
, strips
foo.
, and passes them to the kernel along with params given in modprobe args or
/etc/modprob.d/*.conf
files, in the following order:
-
parameters given via
/etc/modprobe.d/*.conf
:options foo dyndbg=+pt options foo dyndbg # defaults to +p
-
foo.dyndbg
as given in boot args,
foo.
is stripped and passed:foo.dyndbg=" func bar +p; func buz +mp"
-
args to modprobe:
modprobe foo dyndbg==pmf # override previous settings
These
dyndbg
queries are applied in order, with last having final say. This allows boot args to override or modify those from
/etc/modprobe.d
(sensible, since 1 is system wide, 2 is kernel or boot specific), and modprobe args to override both.
In the
foo.dyndbg="QUERY"
form, the query must exclude
module foo
.
foo
is extracted from the param-name, and applied to each query in
QUERY
, and only 1 match-spec of each type is allowed.
The
dyndbg
option is a “fake” module parameter, which means:
- modules do not need to define it explicitly
- every module gets it tacitly, whether they use pr_debug or not
-
it doesn’t appear in
/sys/module/$module/parameters/
To see it, grep the control file, or inspect
/proc/cmdline.
For
CONFIG_DYNAMIC_DEBUG
kernels, any settings given at boot-time (or enabled by
-DDEBUG
flag during compilation) can be disabled later via the sysfs interface if the debug messages are no longer needed:
echo "module module_name -p" > <debugfs>/dynamic_debug/control
Examples
// enable the message at line 1603 of file svcsock.c
nullarbor:~ # echo -n 'file svcsock.c line 1603 +p' >
<debugfs>/dynamic_debug/control
// enable all the messages in file svcsock.c
nullarbor:~ # echo -n 'file svcsock.c +p' >
<debugfs>/dynamic_debug/control
// enable all the messages in the NFS server module
nullarbor:~ # echo -n 'module nfsd +p' >
<debugfs>/dynamic_debug/control
// enable all 12 messages in the function svc_process()
nullarbor:~ # echo -n 'func svc_process +p' >
<debugfs>/dynamic_debug/control
// disable all 12 messages in the function svc_process()
nullarbor:~ # echo -n 'func svc_process -p' >
<debugfs>/dynamic_debug/control
// enable messages for NFS calls READ, READLINK, READDIR and READDIR+.
nullarbor:~ # echo -n 'format "nfsd: READ" +p' >
<debugfs>/dynamic_debug/control
// enable messages in files of which the paths include string "usb"
nullarbor:~ # echo -n '*usb* +p' > <debugfs>/dynamic_debug/control
// enable all messages
nullarbor:~ # echo -n '+p' > <debugfs>/dynamic_debug/control
// add module, function to all enabled messages
nullarbor:~ # echo -n '+mf' > <debugfs>/dynamic_debug/control
// boot-args example, with newlines and comments for readability
Kernel command line: ...
// see whats going on in dyndbg=value processing
dynamic_debug.verbose=1
// enable pr_debugs in 2 builtins, #cmt is stripped
dyndbg="module params +p #cmt ; module sys +p"
// enable pr_debugs in 2 functions in a module loaded later
pc87360.dyndbg="func pc87360_init_device +p; func pc87360_find +p"