核心内容摘要
78赛进13:穿越时光的数字宝藏,重温经典,点燃激情
title: clk-bulkcategories:linuxdriversclktags:linuxdriversclkabbrlink: fc0f43c4date:
09:01:49https://github.com/wdfk-prog/linux-study文章目录drivers/clk/clk-bulk.c 批量时钟控制(Bulk Clock Control) 简化多路时钟管理历史与背景这项技术是为了解决什么特定问题而诞生的它的发展经历了哪些重要的里程碑或版本迭代目前该技术的社区活跃度和主流应用情况如何核心原理与设计它的核心工作原理是什么它的主要优势体现在哪些方面它存在哪些已知的劣势、局限性或在特定场景下的不适用性使用场景在哪些具体的业务或技术场景下它是首选解决方案请例举说明。
是否有不推荐使用该技术的场景为什么对比分析请将其 与 其他相似技术 进行详细对比。
clk_bulk_prepareclk_bulk_enabledrivers/clk/clk-bulk.c 批量时钟控制(Bulk Clock Control) 简化多路时钟管理历史与背景这项技术是为了解决什么特定问题而诞生的drivers/clk/clk-bulk.c文件实现了一套批量时钟控制的辅助API。
这项技术并非一个独立的框架而是对通用时钟框架Common Clock Framework, CCF的一个重要补充和优化。
它的诞生是为了解决设备驱动程序中管理多个时钟资源时的代码冗余、逻辑复杂和易于出错的问题。
许多现代SoC中的复杂外设如显示控制器、GPU、视频处理器往往不只依赖一个时钟而是需要一组时钟例如一个像素时钟、一个总线接口时钟、一个寄存器访问时钟同时被使能才能正常工作。
在没有clk-bulkAPI之前驱动程序必须逐个调用devm_clk_get()来获取每一个时钟的句柄。
编写一个循环来逐个调用clk_prepare_enable()来使能这些时钟。
最关键也是最麻烦的是必须编写复杂的错误处理代码。
如果在使能第五个时钟时失败了驱动程序必须手动地、按相反的顺序去clk_disable_unprepare()前四个已经成功使能的时钟以保证系统状态的一致性。
这种手动管理模式导致驱动的.probe()函数中充满了重复的、冗长的、极易出错的样板代码。
clk-bulkAPI的诞生就是为了将这种“获取一组资源要么全部成功要么全部回滚”的事务性操作抽象成一个简单的函数调用。
它的发展经历了哪些重要的里程碑或版本迭代clk-bulkAPI是随着CCF的成熟和SoC复杂度的增加而自然演进出来的辅助功能。
模式识别内核开发者们在审查大量复杂驱动特别是DRM图形驱动和多媒体驱动时发现上述手动管理多个时钟的模式被反复实现。
API抽象为了消除这种重复社区将这种通用模式抽象出来创建了clk-bulkAPI提供了clk_bulk_get,clk_bulk_prepare_enable,clk_bulk_disable_unprepare等核心函数。
devm集成为了与设备资源管理devres框架更好地集成后续又添加了devm_clk_bulk_get和devm_clk_bulk_get_all等函数使得时钟句柄的获取也能被devres自动管理进一步简化了驱动的资源释放逻辑。
目前该技术的社区活跃度和主流应用情况如何clk-bulkAPI已经成为CCF中一个非常标准和成熟的部分是编写需要管理多个时钟的驱动时的最佳实践。
它被广泛应用于内核中各种复杂的驱动程序尤其是DRMDirect Rendering Manager图形和显示驱动V4L2Video for Linux 2多媒体驱动如视频编解码器、ISPSoC平台驱动中用于管理复杂IP核的驱动核心原理与设计它的核心工作原理是什么clk-bulk的核心原理非常直接它将针对单个时钟的操作封装在一个循环中并内置了健壮的错误处理和回滚unwind逻辑。
批量获取Bulk Get驱动程序通常在设备树中通过clock-names属性列出所有需要的时钟名。
驱动调用devm_clk_bulk_get_all(dev, clks)。
这个函数会解析clock-names属性并在内部循环调用devm_clk_get()来获取每一个时钟的句柄然后将这些句柄填充到一个由调用者提供的数组中。
批量使能Bulk Enable驱动调用clk_bulk_prepare_enable(num_clocks, clks)。
这个函数内部会启动一个循环按顺序对数组中的每一个时钟调用clk_prepare_enable()。
关键的回滚逻辑如果在循环过程中有任何一个clk_prepare_enable()调用失败并返回错误该函数会立即停止。
然后它会自动启动另一个循环按相反的顺序对所有已经成功使能的时钟调用clk_disable_unprepare()从而将时钟状态回滚到调用之前的状态。
最后它将原始的错误码返回给调用者。
批量禁用Bulk Disable驱动调用clk_bulk_disable_unprepare(num_clocks, clks)。
这个函数简单地按相反顺序循环遍历时钟数组并对每个时钟调用clk_disable_unprepare()。
它的主要优势体现在哪些方面代码极大简化将几十行复杂的、带错误处理的循环代码缩减为一两个函数调用。
健壮性将容易出错的回滚逻辑集中在一个地方实现并经过充分测试极大地提高了驱动程序的健壮性避免了开发者因疏忽而导致的资源泄漏或状态不一致。
可读性驱动代码的意图变得更加清晰。
clk_bulk_prepare_enable明确地表达了“将这一组时钟作为一个整体进行使能”的意图。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性缺乏个性化操作clk-bulkAPI将所有时钟视为一个同质的集合统一进行使能或禁用。
它不提供对组内单个时钟进行独立操作如单独设置某个时钟的频率的接口。
如果需要这类操作驱动仍然需要获取单个时钟的句柄并调用标准的clk_*API。
“全体一致”模型该API适用于所有时钟需要被同时打开或关闭的场景。
如果一组时钟有不同的生命周期例如时钟A需要一直开启而时钟B仅在特定操作时开启那么它们就不应该被放在同一个批量操作中。
使用场景在哪些具体的业务或技术场景下它是首选解决方案请例举说明。
clk-bulk是任何**一个设备驱动需要依赖两个或更多时钟并且这些时钟的生命周期一致需要同时开启和关闭**时的首选解决方案。
场景一DRM显示控制器驱动一个显示控制器CRTC可能需要一个bus_clk来访问寄存器一个axi_clk来进行内存访问以及一个pixel_clk来驱动显示面板。
这三个时钟必须全部开启显示通路才能工作。
驱动程序会在.enable()回调中调用clk_bulk_prepare_enable()在.disable()回调中调用clk_bulk_disable_unprepare()。
场景二视频解码器驱动一个H.264硬件解码器可能需要一个core_clk用于其主逻辑一个mem_clk用于访问帧缓冲以及一个reg_clk用于CPU访问。
这些时钟也构成了一个必须被整体管理的集合。
场景三复杂的SoC外设一个集成了SPI主控和DMA引擎的IP核可能需要spi_clk和dma_clk。
如果驱动的设计是让它们协同工作那么使用clk-bulkAPI来管理这两个时钟会非常方便。
是否有不推荐使用该技术的场景为什么单时钟设备如果一个设备只需要一个时钟使用clk-bulkAPI纯属画蛇添足标准的devm_clk_get()和clk_prepare_enable()更简单直接。
生命周期不同的多时钟设备如果一个设备有多个时钟但它们的开关时机不同。
例如一个bus_clk需要在整个驱动生命周期内保持开启而一个高功耗的core_clk仅在执行计算密集型任务时才被短暂开启。
这种情况下这两个时钟必须被独立管理。
对比分析请将其 与 其他相似技术 进行详细对比。
clk-bulkAPI的主要对比对象就是不使用它而是手动实现相同逻辑的“DIY”方法。
特性clk-bulkAPI手动循环管理实现方式调用devm_clk_bulk_get_all()和clk_bulk_prepare_enable()等高级API。
在驱动中编写for循环逐个调用devm_clk_get()和clk_prepare_enable()。
代码复杂度极低。
几行代码即可完成。
高。
需要编写获取、使能、错误检查和错误回滚的完整逻辑代码量大且嵌套深。
健壮性高。
核心的回滚逻辑是标准化的、经过充分测试的。
中到低。
每个驱动的实现质量参差不齐极易在回滚逻辑中引入bug。
可读性和维护性高。
代码简洁意图清晰。
低。
大量的样板代码掩盖了驱动的核心逻辑难以阅读和维护。
开发效率高。
开发者无需花费时间在重复的、易错的基础设施代码上。
低。
需要为每个驱动编写和调试相同的底层逻辑。
功能提供一个“全有或全无”的事务性保证。
功能取决于开发者的实现但通常目标是实现与clk-bulk相同的功能。
clk_bulk_prepare/** * clk_bulk_prepare - prepare a set of clocks * num_clks: the number of clk_bulk_data * clks: the clk_bulk_data table being prepared * * clk_bulk_prepare may sleep, which differentiates it from clk_bulk_enable. * Returns 0 on success, -EERROR otherwise. */int__must_checkclk_bulk_prepare(intnum_clks,conststructclk_bulk_data*clks){intret;inti;for(i0;inum_clks;i){retclk_prepare(clks[i].clk);if(ret){pr_err(Failed to prepare clk %s: %d\n,clks[i].id,ret);gotoerr;}}return0;err:clk_bulk_unprepare(i,clks);returnret;}EXPORT_SYMBOL_GPL(clk_bulk_prepare);clk_bulk_enable/** * clk_bulk_enable - ungate a set of clocks * num_clks: the number of clk_bulk_data * clks: the clk_bulk_data table being ungated * * clk_bulk_enable must not sleep * Returns 0 on success, -EERROR otherwise. */int__must_checkclk_bulk_enable(intnum_clks,conststructclk_bulk_data*clks){intret;inti;for(i0;inum_clks;i){retclk_enable(clks[i].clk);if(ret){pr_err(Failed to enable clk %s: %d\n,clks[i].id,ret);gotoerr;}}return0;err:clk_bulk_disable(i,clks);returnret;}EXPORT_SYMBOL_GPL(clk_bulk_enable);