// 你以为这是正常的GPIO?
&gpio1 {
pinctrl-0 = <&pinctrl_led>;
led-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
};
就多了个1,我查了四五个小时。
事情是这样的。之前做一块i.MX6ULL的板子,客户说LED不亮。硬件那边信誓旦旦说电路没问题,飞线测试GPIO_05电平也是对的。
那我只能怀疑软件了。
先确认设备树。i.MX6ULL一共有5组GPIO,从gpio1到gpio5。LED接在GPIO1_IO05上,按照IMX的惯例,在设备树里应该这么写:
led-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
但是我翻dts的时候瞟了一眼,发现同事写的是:
led-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
等一下,这个 &gpio1 对吗?IMX6ULL的设备树里,gpio1的label定义在imx6ul.dtsi里:
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>;
};
看上去没问题对吧?phandle是gpio1,#gpio-cells是2,所以 <&gpio1 5 GPIO_ACTIVE_LOW> 语法完全正确。
但是Linux起来以后,ls /sys/class/gpio/ 下面就是没有这个LED。
试过直接操作/sys/class/gpio/export——也是报错"Device or resource busy"。dmesg里也没有明显的错误信息,只有一个莫名其妙的:
gpiochip_setup_dev: registered GPIOs 0 to 31 on device: gpiochip0
等等,0到31?那不是gpio1全部32个pin都注册了吗?那为什么我申请gpio1_5还会失败?
然后我做了嵌入式Linux工程师最常做的事——把kernel的gpio调试全打开。
重新编译内核,打开CONFIG_GPIO_SYSFS、CONFIG_GPIO_DEBUGFS,起来以后挂载debugfs:
# mount -t debugfs none /sys/kernel/debug
# cat /sys/kernel/debug/gpio
输出里赫然写着:
gpiochip0: GPIOs 0-31, parent: platform/209c000.gpio, gpio1:
gpio-5 ( ) in hi
in?hi?我明明在设备树里设了GPIO_ACTIVE_LOW,而且它怎么会是输入模式?
折腾了半天,差点就要怀疑是pinctrl没配好了。把pinctrl节点翻了个底朝天,确认pinctrl_led确实把MX6UL_PAD_GPIO1_IO05配成了output。
最后怎么发现的呢?
我在debugfs里一个一个看gpiochip的注册情况,发现了一个诡异的事情——
gpiochip0: GPIOs 0-31, parent: platform/209c000.gpio, gpio1:
gpiochip1: GPIOs 32-63, parent: platform/20a0000.gpio, gpio2:
...
gpiochip4: GPIOs 128-159, parent: platform/20ac000.gpio, gpio5:
gpiochip0到gpiochip4,一共5组,没问题。但我在debugfs里搜索"led"的时候发现——
gpio-504 ( ) in lo
gpio-504?504 - 32*15...不对。等等,我数一下我板子上扩展的GPIO Expander注册在了哪里。
好家伙——问题在这儿。我在另一颗I2C GPIO expander(pca9535)的设备树里,也写了一个led子节点,而且那个子节点里也用了 led-gpios 这个属性名。更致命的是,我在pca9535的节点里写的是:
&i2c1 {
pca9535: gpio@20 {
compatible = "nxp,pca9535";
reg = <0x20>;
gpio-controller;
#gpio-cells = <2>;
led {
label = "board-led";
gpios = <&pca9535 15 GPIO_ACTIVE_LOW>;
};
};
};
注意看!我用的是 gpios 而不是 led-gpios,所以这其实不会跟主芯片的LED冲突。但是当我搜"led-gpios"的时候,我发现同一个dts文件里还写了另一行:
led-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
在一个完全不相关的节点里。那个节点是个regulator!regulator节点里的某个属性不小心写成了led-gpios而不是regulator-gpios。而这个regulator probe的时候去拿这个gpio,拿走了gpio1_5,设置成输入模式就不放了。等我真正的led驱动再去claim同一个pin,自然就EBUSY了。
全TM因为一个拼写错误。
说实话,设备树debug就是这么原始——没编译器帮你检查属性名,写错一个字母,或者少写个s,Linux内核就默默地走另一个code path,根本不会告诉你"哎你这属性名是不是写错了"。
后来我养成了个习惯,每次改dts之后都干两件事。
第一件,编译完dtb后马上反编译确认:
dtc -I dtb -O dts /sys/firmware/fdt | grep -A5 "led"
一眼扫过去,属性名对不对,引用的phandle对不对,都是纯体力活。但最有用。
第二件,板子起来以后第一件事就是看debugfs gpio:
cat /sys/kernel/debug/gpio | grep -E "gpio-[0-9]+.*led"
如果这个pin的label不是你预期的驱动名字,那肯定有别人捷足先登了。
还有一个骚操作——如果你不确定某个phandle到底指向谁,可以直接用dtc把fdt dump出来全文搜。dtc的-O dts输出会把phandle数值改成label名,所以<&gpio1 5>会原样显示出来,特别直观。
说到这我想起来另一个坑。有次我写spi设备节点,reg = <0> 忘了把cs-gpios对应的片选pin配成output模式,结果spi传输一直超时。查了三天,最后发现是bootloader里那个pin被设成了输入——kernel的pinctrl又没覆盖它,spi controller probe的时候读到的电平是浮空的。
所以说嵌入式Linux开发,八成的问题最终都落在三块:设备树写错了、pinctrl没配好、电源没供上。剩下两成是野指针。
对了,你们遇到过最无语的设备树bug是什么?