目标
给设备树动态增加一个gpio节点,开发测试相关的驱动,并且不重新打包系统镜像,而是采用overlay热更新设备树的方式。
先写个用作overlay的设备树
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target-path = "/";
__overlay__ {
tskeys {
compatible = "dxl,keys";
ts_led {
gpios = <&pio 0 7 0>;
};
};
};
};
};
target-path
是定位到“/”跟节点,同样也可以使用taget = &xxx
来定位到引用节点;- 设置了匹配符还有GPIO引脚信息。
将设备树编译成dtbo
dtc -I dts -O dtb ./ts.dts > ts.dtbo
将ts.dtbo放到设备的/lib/firmware目录下
cp ts.dtbo /lib/firmware/
/lib/firmware/
目录,是overlay机制默认搜索路径之一!
编写个热记载设备树overlay的函数
dabo_t *dt_apply_by_overlay(const char *pdev_name, const char *dtbo_path)
{
// dabo_t *pdabo;
int ret;
struct platform_device *pdev = NULL;
const struct firmware *fw;
int ovcs_id = 0;
/**创建虚拟设备 */
pdev = platform_device_alloc(pdev_name, PLATFORM_DEVID_NONE);
if (IS_ERR(pdev))
{
pr_err("创建虚拟设备 %s 失败!\n", pdev_name);
return ERR_CAST(pdev);
}
ret = platform_device_add(pdev);
if (ret)
{
pr_err("Failed to invoke platform_device_add\n");
goto _err_0;
}
/**从用户空间请求DTBO */
ret = request_firmware(&fw, dtbo_path, &pdev->dev);
if (ret)
{
pr_err("Failed to invoke request_firmware\n");
goto _err_1;
}
/**合并设备树 */
ret = of_overlay_fdt_apply(fw->data, fw->size, &ovcs_id);
release_firmware(fw);
if (ret)
{
pr_err("Failed to invoke of_overlay_fdt_apply\n");
goto _err_1;
}
dabo_t *pdabo = kzalloc(sizeof(dabo_t), GFP_KERNEL);
if (IS_ERR(pdabo))
{
goto _err_2;
}
pdabo->pdev = pdev;
pdabo->ovcs_id = ovcs_id;
return pdabo;
_err_2:
of_overlay_remove(&ovcs_id);
_err_1:
platform_device_del(pdev);
_err_0:
platform_device_put(pdev);
return ERR_PTR(ret);
}
- 使用
request_firmware
加载/lib/firmware/xx.dtbo
overlay设备树文件 - 使用
of_overlay_fdt_apply
将其合并到内核主设备树结构中
与此对应,还要有相关的资源释放函数:
void dt_remove(dabo_t *dabo)
{
of_overlay_remove(&dabo->ovcs_id);
platform_device_del(dabo->pdev);
platform_device_put(dabo->pdev);
kfree(dabo);
}
使用它
-
在驱动入口函数中先调用刚写的热加载函数,然后再注册平台驱动
static int __init test_key_init(void) { int ret = 0; gd = ERR_PTR(-EBUSY); /**overlay合并设备树 */ dabo = dt_apply_by_overlay("tskey_v", "ts.dtbo"); if (IS_ERR(dabo)) { pr_err("Failed to invoke dt_apply_by_overlay\n"); return PTR_ERR(dabo); } /**平台驱动 */ ret = platform_driver_register(&pdrv); if (ret) { pr_err("Failed to invoke platform_driver_register\n"); return ret; } pr_info("!done!\n"); return 0; }
dt_apply_by_overlay
调用之后,通过overlay机制,ts.dtbo就被合并到主设备树了!- 此后,在probe中就可以正常处理热更新的设备树节点了,比如:
static int _probe(struct platform_device *pdev) { struct fwnode_handle *child; pr_info("probe..\n"); /**正常获取gpio信息 */ device_for_each_child_node(&pdev->dev, child) { // 这里就正常去处理热更新增加的gpio节点了!! gd = devm_fwnode_get_gpiod_from_child(&pdev->dev, NULL, child, GPIOD_OUT_LOW, "label_ts"); if (IS_ERR(gd)) { dev_err(&pdev->dev, "devm_fwnode_get_gpiod_from_child failed! rc: %d\n", PTR_ERR(gd)); return PTR_ERR(gd); } full_name = of_node_full_name(to_of_node(child)); break; } /**注册file_operations结构体 */ static struct file_operations fop = { .owner = THIS_MODULE, .open = _open, .write = _write, .read = _read, }; major = register_chrdev(0, "tskey", &fop); if (major <= 0) { dev_err(&pdev->dev, "注册chrdev失败!\n"); gpiod_put(gd); return major; } /**创建class */ _cls = class_create(THIS_MODULE, "tskeys"); /**创建char节点 */ _dev = device_create(_cls, NULL, MKDEV(major, 0), NULL, full_name); // 等等操作..... return 0; }
- 最后别忘在出口函数中释放先前热加载的设备树
static void __exit test_key_exit(void) { dt_remove(dabo); // 这里使用我们实现的释放函数! platform_driver_unregister(&pdrv); pr_info("exit!\n"); }