Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions document/qa/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ChapterNav>
<ChapterLink num="31" href="qa_31">Docker 构建镜像更新相关疑问</ChapterLink>
<ChapterLink num="51" href="qa_51">Beep 的 compatible 字符串没有匹配上,但仍然能驱动蜂鸣器</ChapterLink>
</ChapterNav>

::: tip 有问题?
Expand Down
213 changes: 213 additions & 0 deletions document/qa/qa_51.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Issue #51: Beep 的 compatible 字符串没有匹配上,但仍然能驱动蜂鸣器

## 问题描述

驱动的 `compatible` 字符串与设备树不一致,但蜂鸣器仍能正常工作。

- 驱动代码:`compatible = "my,beep"`
- 设备树:`compatible = "imxaes_beep"`
- 驱动成功加载并控制 GPIO

## 结论

**驱动不是靠 `compatible` 匹配上的,而是靠 platform bus 的最后兜底规则:`platform_device.name == platform_driver.driver.name`。**

## 源码分析

### Platform 驱动匹配顺序

Linux 主线内核 [drivers/base/platform.c](https://github.com/torvalds/linux/blob/master/drivers/base/platform.c):

```c
// 第 1376-1399 行
static int platform_match(struct device *dev, const struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* 1. 当设置了 driver_override 时,只匹配指定驱动 */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);

/* 2. 首先尝试 OF(设备树)风格匹配 */
if (of_driver_match_device(dev, drv))
return 1;

/* 3. 然后尝试 ACPI 风格匹配 */
if (acpi_driver_match_device(dev, drv))
return 1;

/* 4. 尝试匹配 id_table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* 5. 兜底:驱动名称匹配 ⬅️ Beep 驱动走的是这里 */
return (strcmp(pdev->name, drv->name) == 0);
}
```

### 匹配流程图

```
┌─────────────────────────┐
│ platform_match() │
└───────────┬─────────────┘
┌─────────────────────────┐
│ driver_override? │
└───────────┬─────────────┘
│ No
┌─────────────────────────┐
│ OF 匹配 │
│ (of_match_table) │
└───────────┬─────────────┘
│ 失败
│ "my,beep" ≠ "imxaes_beep"
┌─────────────────────────┐
│ ACPI 匹配 │
└───────────┬─────────────┘
│ 失败
┌─────────────────────────┐
│ id_table 匹配 │
└───────────┬─────────────┘
│ 失败
┌─────────────────────────┐
│ ⭐ 兜底:name 匹配 │
│ strcmp(pdev->name, │
│ drv->name) == 0 │
└───────────┬─────────────┘
"beep" == "beep" ✅ 匹配成功!
```

### 实际发生了什么

**Beep 驱动案例:**

| 项目 | 值 |
|------|-----|
| 设备树节点名 | `beep` |
| 设备树 compatible | `imxaes_beep` |
| 驱动 .driver.name | `beep` |
| 驱动 of_match_table | `my,beep` |

```
设备树: beep { compatible = "imxaes_beep"; ... }
内核解析: platform_device.name = "beep"
驱动注册: platform_driver.driver.name = "beep"
OF 匹配: "my,beep" ≠ "imxaes_beep" ❌
兜底匹配: "beep" == "beep" ✅
probe() 成功调用!
```

### 为什么 GPIO 还能正常工作?

即使不是通过 compatible 匹配,设备树节点仍然挂在 `pdev->dev.of_node` 上:

```c
// beep_driver.c probe 函数
dev->gpio = devm_gpiod_get(&pdev->dev, "beep", GPIOD_OUT_HIGH);
// ^^^^^^^^^^^^
// 仍能访问设备树节点
```

驱动可以通过 `pdev->dev.of_node` 访问完整的设备树节点,包括 `beep-gpio` 属性。

## 验证方法

```bash
# 1. 查看 platform device
ls /sys/bus/platform/devices/
# 输出包含: beep

# 2. 查看 modalias(显示 OF compatible)
cat /sys/bus/platform/devices/beep/modalias
# 输出: of:NbeepTimxaes_beepCimxaes_beep
# ^^^^节点名 ^^^^^^^^^^^^compatible

# 3. 查看绑定关系
readlink /sys/bus/platform/devices/beep/driver
# 输出: ../../../bus/platform/drivers/beep

# 4. 查看驱动模块信息
strings /lib/modules/beep_driver.ko | grep compatible
# 只有: my,beep(没有 imxaes_beep)
```

## 建议

**修正驱动和设备树,使用标准 compatible 格式:**

```c
// beep_driver.c
static const struct of_device_id beep_of_match[] = {
{ .compatible = "imxaes,beep" },
{ /* sentinel */ }
};
```

```dts
// imx6ull-aes-beep.dts
beep {
compatible = "imxaes,beep";
beep-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>; // 标准复数形式
};
```

**为什么不应该依赖 name 兜底匹配:**

| 问题 | 说明 |
|------|------|
| 模块无法自动加载 | modalias 基于 compatible,不会触发正确的模块加载 |
| 代码可读性差 | compatible 不匹配会让维护者困惑 |
| 可能冲突 | 多个驱动使用相同 name 时会冲突 |

**开发板验证结果:**

```bash
# 1. 模块中的 compatible 已更新
~ # strings /lib/modules/beep_driver.ko | grep -E "my,beep|imxaes,beep|imxaes_beep"
imxaes,beep
alias=of:N*T*Cimxaes,beepC*
alias=of:N*T*Cimxaes,beep

# 2. 设备树中的 compatible(已同步更新)
~ # cat /sys/firmware/devicetree/base/beep/compatible
imxaes,beep

# 3. 驱动加载成功
~ # insmod /lib/modules/beep_driver.ko
[ 186.343069] beep_driver: loading out-of-tree module taints kernel.
[ 186.345357] beep beep: Beep driver loaded successfully

# 4. 应用程序控制蜂鸣器正常(./beep 来自 [driver/application/beep](../../driver/application/beep/))
~ # ./beep 0
Input: 0 -> Beep turned ON
[ 194.780173] beep: device opened
[ 194.780241] beep: ON (GPIO set to LOW)
[ 194.780756] beep: device released

~ # ./beep 1
Input: 1 -> Beep turned OFF
[ 196.957548] beep: device opened
[ 196.957620] beep: OFF (GPIO set to HIGH)
[ 196.958057] beep: device released
```

现在驱动通过 **OF compatible 匹配**工作,而非 name 兜底匹配。

## 相关链接

- [Linux Platform Driver 文档](https://docs.kernel.org/driver-api/driver-model/platform.html)
- [设备树规范](https://devicetree-specification.readthedocs.io/)
2 changes: 1 addition & 1 deletion driver/beep/alpha-board/beep_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ static void beep_remove(struct platform_device *pdev)

/* 匹配设备树中的 compatible */
static const struct of_device_id beep_of_match[] = {
{ .compatible = "my,beep" },
{ .compatible = "imxaes,beep" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, beep_of_match);
Expand Down
12 changes: 6 additions & 6 deletions driver/device_tree/alpha-board/beep/imx6ull-aes-beep.dts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@
*/
beep {
// PS:下面的内容可写可不写,这其实是给总线设备的。。。
#address-cells = <1>;
#size-cells = <1>;
#address-cells = <1>;
#size-cells = <1>;
// 但是出于尊重之前的正点的教程,这里留下,但是要反复强调可以删除

compatible = "imxaes_beep";
compatible = "imxaes,beep";

pinctrl-names = "default";
pinctrl-0 = <&pinctrl_beep>;
pinctrl-0 = <&pinctrl_beep>;

status = "okay";
// 这里是我们的GPIO,属性是自定义的,
// 虽然实际上更标准的做法是gpios
beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
beep-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/dts-v1/;
#include "imx6ull.dtsi"
#include "imx6ull-aes.dtsi"

/ {
model = "Awesome Embedded Studio IMX6ULL Example Driver";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
/**
* PS 下,可以看到我们在/下追加了一个新的LED等节点
* 这里也是我们之后寻找LED的Path /imx_aes_led
*/
imx_aes_led {
// PS:下面的内容可写可不写,这其实是给总线设备的。。。
#address-cells = <1>;
#size-cells = <1>;
// 但是出于尊重之前的正点的教程,这里留下,但是要反复强调可以删除
// LED设备完全用不到

// 现在不能随便抄了,驱动和设备树建议严格对齐,内核不知道为啥搞了一套贼复杂的
// 兜底机制,这很容易出错的其实
compatible = "imxaes,led";

pinctrl-names = "default";
pinctrl-0 = <&pinctrl_aes_led>;

status = "okay";
// 这里是我们的GPIO,属性是自定义的,
// 虽然实际上更标准的做法是gpios
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
};
};

&iomuxc {
// 使用pinctrl子系统添加的LED节点驱动
// &iomuxc : 引用追加语法,必须这样写
// pinctrl_aes_led: 标签(label),供其他节点引用 pinctrl-0 = <&pinctrl_aes_led>
// led_grp : 节点名,命名可自由选择
// fsl,pins : NXP i.MX pinctrl绑定定义的必需属性名
// fsl, pins等是IMX SOC系列的关键字,不是通用的,这说明的是
// 对应的GPIO节点和它的配置的值,这个值怎么来的可以看看之前的LED教程
// 这是根据手册的PAD每一个位的含义算出来的
pinctrl_aes_led: led_grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
};
57 changes: 57 additions & 0 deletions driver/platform_led_13/alpha-board/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# LED Driver using platform API Makefile
#
# 由 template_creator.sh 自动生成

# Kernel module definition
obj-m := platform_led_13_driver.o
platform_led_13_driver-y := platform_led_13_driver_main.o led_hw.o

# ── 项目配置 ────────────────────────────────────────
PROJECT_ROOT := $(shell realpath $(CURDIR)/../..)
ARCH := arm
CROSS_COMPILE := arm-none-linux-gnueabihf-

# 内核源码路径
KDIR := $(PROJECT_ROOT)/third_party/linux-${KERNEL_TYPE}
KOBJ := $(PROJECT_ROOT)/out/${KERNEL_TYPE}

# 输出目录
OUTPUT_DIR := $(PROJECT_ROOT)/out/driver_artifacts/platform_led_13/alpha-board

.PHONY: all modules clean install help

all: modules

modules:
@echo "🔨 编译platform_led_13驱动..."
@mkdir -p $(OUTPUT_DIR)
$(MAKE) -C $(KDIR) M=$(CURDIR) O=$(KOBJ) \
ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
@cp *.ko $(OUTPUT_DIR)/ 2>/dev/null || true
@echo "✓ 驱动编译完成: $(OUTPUT_DIR)/platform_led_13_driver.ko"

clean:
@echo "🧹 清理构建产物..."
$(MAKE) -C $(KDIR) M=$(CURDIR) O=$(KOBJ) \
ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean 2>/dev/null || true
@rm -rf $(OUTPUT_DIR)

install: modules
@echo "📦 驱动位置: $(OUTPUT_DIR)"
@ls -lh $(OUTPUT_DIR)/*.ko 2>/dev/null || echo "无.ko文件"
@echo ""
@echo "使用方法:"
@echo " 1. 构建驱动: scripts/driver_helper/build_driver.sh platform_led_13 alpha-board"
@echo " 2. 部署驱动: scripts/driver_helper/deploy_driver.sh platform_led_13 alpha-board"
@echo " 3. 测试驱动: insmod platform_led_13_driver.ko"
@echo " 4. 查看日志: dmesg | tail"

help:
@echo "LED Driver using platform API"
@echo ""
@echo "用法:"
@echo " make - 编译驱动"
@echo " make clean - 清理构建产物"
@echo " make install - 显示使用说明"
@echo ""
@echo "产物位置: $(OUTPUT_DIR)"
Loading
Loading