使用debugfs

在进行Linux驱动开发时经常见到使用pr_debug和dev_dbg打印驱动的log,如果在内核配置时选择了CONFIG_DYNAMIC_DEBUG宏,那么就可以利用类似下面的命令打开对应文件的log:

echo -n “file xxx.c +p” > /sys/kernel/debug/dynamic_debug/control
但是有时候我们需要看到这个文件在内核启动阶段的log,那么改怎么办呢?

这里有两种方法:

方法一 修改内核传参

修改bootloader传递给kernel的bootargs,如果使用了设备树的话,可以修改在chosen节点中bootargs属性的值,具体方法内核文档:Documentation/dynamic-debug-howto.txt

这种方案的优点是不需要修改驱动代码。

比如我们需要开机内核启动的时候就打开tfa98xx.c、wcd-mbhc-v2.c和q6asm.c的log,首先我们可以看一下这两个文件对应的KBUILD_MODNAME,这里有两种方法可以查看这个值:

查看对应的Makefile
在驱动中查看上面的三个文件对应的Makefile,如下:

snd-soc-tfa98xx-objs := tfa98xx.o tfa_container.o tfa_dsp.o tfa9887B_init.o tfa9887_init.o tfa9888_init.o tfa9890_init.o tfa9891_init.o tfa9897_init.o
obj-$(CONFIG_SND_SOC_TFA98XX) += snd-soc-tfa98xx.o

snd-soc-wcd-mbhc-objs := wcd-mbhc-v2.o
obj-$(CONFIG_SND_SOC_WCD_MBHC) += snd-soc-wcd-mbhc.o

obj-y += audio_calibration.o audio_cal_utils.o q6adm.o q6afe.o q6asm.o
q6audio-v2.o q6voice.o q6core.o rtac.o q6lsm.o audio_slimslave.o
其中,tfa98xx.c对应的KBUILD_MODNAME就是snd-soc-tfa98xx,wcd-mbhc-v2.c对应的KBUILD_MODNAME就是snd-soc-wcd-mbhc,q6asm.c对应的KBUILD_MODNAME是q6asm

第二种方法如下,用如下命令打开这三个文件的log,那么在这三个文件输出log的同时也会将对应的KBUILD_MODNAME也输出出来
adb shell ‘echo -n “file tfa98xx.c +pmflt” > /sys/kernel/debug/dynamic_debug/control’
adb shell ‘echo -n “file wcd-mbhc-v2.c +pmflt” > /sys/kernel/debug/dynamic_debug/control’
adb shell ‘echo -n “file q6asm.c +pmflt” > /sys/kernel/debug/dynamic_debug/control’
其中涉及到的符号的含义如下:

The flags specification comprises a change operation followed
by one or more flag characters. The change operation is one
of the characters:

- 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)
然后执行相关的操作,让这几个文件输出log,在log中可以看到:

[ ] snd_soc_tfa98xx:tfa98xx_mute:: tfa98xx -: state:
[ : q6asm_callback: nowait_cmd_cnt

[ ] snd_soc_wcd_mbhc:wcd_correct_swch_plug:: wcd_correct_swch_plug: hs_comp_res:
从上面的log中可以看到,第一个冒号前面的字符串就是对应的KBUILD_MODNAME。

在获得了KBUILD_MODNAME后,就可以修改设备树文件了,下面是在原有bootargs后追加后的结果。

chosen {
    bootargs = "sched_enable_hmp=1 sched_enable_power_aware=1 snd_soc_wcd_mbhc.dyndbg=\"file wcd-mbhc-v2.c +p\" snd_soc_tfa98xx.dyndbg=\"file tfa98xx.c +p; file tfa_dsp.c +p\"";
};

以 snd_soc_tfa98xx.dyndbg=“file tfa98xx.c +p; file tfa_dsp.c +p” 为例说明一下:

等号前面的命名规则是 “KBUILD_MODNAME.dyndbg”,等号后面的比较好理解,需要注意的是需要对双引号进行转义。

在调试这部分时可以打开kernel/params.c的log,方法是文件的开头定义DEBUG宏,这样就会将这个文件的pr_debug和dev_dbg打开。

这样在kernel启动的时候就可以看到对命令行的解析过程:

<>[ 0.015794] doing dyndbg params, parsing ARGS: ‘sched_enable_hmp=1 sched_enable_power_aware=1 snd_soc_wcd_mbhc.dyndbg=“file wcd-mbhc-v2.c +p” snd_soc_tfa98xx.dyndbg=“file tfa98xx.c +p; file tfa_dsp.c +p” console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0 androidboot.hardware=qcom user_debug=31 msm_rtb.filter=0x237 ehci-hcd.park=3 lpm_levels.sleep_disabled=1 zswap.enabled=1 cma=32M@0-0xffffffff loglevel=0 androidboot.bootdevice=624000.ufshc androidboot.verifiedbootstate=orange androidboot.veritymode=logging androidboot.serialno=d94b873f androidboot.fingerprint.id=fpc androidboot.hardware.id=0x1c uart_enable=0 ro.housing.color=black pmode=0 androidboot.baseband=msm mdss_mdp.panel=1:dsi:0:qcom,mdss_dsi_nt35597_dsc_wqxga_cmd:config2:1:none:cfg:single_dsi fpsimd.fpsimd_settings=0’
<>[ ’
<>[ ’
<>[ 0.015824] doing dyndbg params: snd_soc_wcd_mbhc.dyndbg=‘file wcd-mbhc-v2.c +p’
<>[ 0.015901] doing dyndbg params: snd_soc_tfa98xx.dyndbg=‘file tfa98xx.c +p; file tfa_dsp.c +p’
<>[ 0.015975] doing dyndbg params: console=‘ttyHSL0,115200,n8’
<>[ 0.015980] doing dyndbg params: androidboot.console=‘ttyHSL0’
<>[ 0.015986] doing dyndbg params: androidboot.hardware=‘qcom’
<>[ ’
<>[ 0.015996] doing dyndbg params: msm_rtb.filter=‘0x237’
<>[ ’
<>[ ’
<>[ ’
<>[ 0.016016] doing dyndbg params: cma=‘32M@0-0xffffffff’
<>[ ’
<>[ 0.016026] doing dyndbg params: androidboot.bootdevice=‘624000.ufshc’
<>[ 0.016031] doing dyndbg params: androidboot.verifiedbootstate=‘orange’
<>[ 0.016036] doing dyndbg params: androidboot.veritymode=‘logging’
<>[ 0.016041] doing dyndbg params: androidboot.serialno=‘d94b873f’
<>[ 0.016046] doing dyndbg params: androidboot.fingerprint.id=‘fpc’
<>[ 0.016051] doing dyndbg params: androidboot.hardware.id=‘0x1c’
<>[ ’
<>[ 0.016061] doing dyndbg params: ro.housing.color=‘black’
<>[ ’
<>[ 0.016070] doing dyndbg params: androidboot.baseband=‘msm’
<>[ 0.016075] doing dyndbg params: mdss_mdp.panel=‘1:dsi:0:qcom,mdss_dsi_nt35597_dsc_wqxga_cmd:config2:1:none:cfg:single_dsi’
<>[ ’
方法二 在需要开启log的驱动文件的开头定义宏DEBUG

这样该驱动文件中的pr_debug和dev_dbg就可以打开了。

比如驱动文件的名字是tfa98xx.c,那么就在其第一个非注释行添加DEBUG宏的定义:

/*

  • tfa98xx.c tfa98xx codec module
  • Copyright © 2015 NXP Semiconductors
  • Author: Sebastien Jan [email protected]
  • This program is free software; you can redistribute it and/or modify it
  • under the terms of the GNU General Public License as published by the
  • Free Software Foundation; either version 2 of the License, or (at your
  • option) any later version.
    */

#define DEBUG
#define pr_fmt(fmt) "%s(): " fmt, func

#include <linux/module.h>
#include <linux/i2c.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/of_gpio.h>

为什么这样做可以实现呢?下面我们以pr_debug为例简单分析。

在内核配置了CONFIG_DYNAMIC_DEBUG后,pr_debug的定义如下:

#define pr_debug(fmt, …)
dynamic_pr_debug(fmt, ##VA_ARGS)
dynamic_pr_debug的定义如下:

#define dynamic_pr_debug(fmt, …)
do {
DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt);
if (unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT))
__dynamic_pr_debug(&descriptor, pr_fmt(fmt),
##VA_ARGS);
} )
这里用到了宏DEFINE_DYNAMIC_DEBUG_METADATA,定义如下:

#define DEFINE_DYNAMIC_DEBUG_METADATA(name, fmt)
)
attribute((section("__verbose"))) name = {
.modname = KBUILD_MODNAME,
.function = func,
.filename = FILE,
.format = (fmt),
.lineno = LINE,
.flags = _DPRINTK_FLAGS_DEFAULT,
}
__dynamic_pr_debug的定义如下:

void __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, …)
{
va_list args;
struct va_format vaf;
char buf[PREFIX_SIZE];

BUG_ON(!descriptor);
BUG_ON(!fmt);

va_start(args, fmt);

vaf.fmt = fmt;
vaf.va = &args;

printk(KERN_DEBUG "%s%pV", dynamic_emit_prefix(descriptor, buf), &vaf);

va_end(args);

}
从上面的代码可以看到,每一个pr_debug都对应一个名为descriptor,类型为struct _ddebug的变量,存放在kernel的__verbose段。决定这个pr_debug能否输出log的条件就是descriptor.flags & _DPRINTK_FLAGS_PRINT为true。

在定义descriptor时,将其flags成员赋值为了_DPRINTK_FLAGS_DEFAULT,下面看一下这两个宏的定义:

#if defined DEBUG
#define _DPRINTK_FLAGS_DEFAULT _DPRINTK_FLAGS_PRINT
#else
#define _DPRINTK_FLAGS_DEFAULT 0
#endif
可以看到,如果定义了宏DEBUG,那么_DPRINTK_FLAGS_DEFAULT其实就是_DPRINTK_FLAGS_PRINT,所以默认就是可以打印的。如果没有定义,那么_DPRINTK_FLAGS_DEFAULT就是0,上面的条件不会成立,也就打印不出来。

在dynamic debug初始化的时候会遍历__verbose段,处理每一个struct _ddebug类型的变量,如果定义了DEBUG宏,在开机后,可以读取control节点,会发现已经有"p"参数了。

root@colombo:/ # cat /d/dynamic_debug/control | grep tfa
sound/soc/codecs/tfa98xx.c: [snd_soc_tfa98xx]tfa98xx_mute =p “state: %d\012”
sound/soc/codecs/tfa98xx.c: [snd_soc_tfa98xx]tfa98xx_digital_mute =p “%s enter, mute: %d\012”
sound/soc/codecs/tfa98xx.c: [snd_soc_tfa98xx]tfa98xx_info_vstep =p “vsteps count: %d [prof=%d]\012”
… …
当然,也可以使用下面的命令关闭:

echo -n “file xxx.c -p” > /sys/kernel/debug/dynamic_debug/control
完。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章