基于FPGA寄存器的Linux时钟源适配问题咨询
使用clocksource是实现FPGA时钟作为系统时钟源的正确方案,但你的驱动存在几个关键缺陷,导致计时偏差和调度器panic,以下是具体问题和修复方法:
核心问题1:寄存器读取的原子性缺失
FPGA时钟的秒寄存器和亚秒寄存器是独立更新的,两次readl_relaxed之间可能发生秒进位(比如亚秒寄存器溢出导致秒寄存器加1),这会读取到不一致的秒/亚秒组合,造成时间回退——而clocksource要求时钟必须单调递增,时间回退会直接触发调度器panic。
修复方法:循环读取直到秒寄存器值稳定,确保两次读取的秒值一致:
static u64 _FPGA_TIME_clocksource_read(void) { u64 counter; u32 seconds, subseconds, seconds_check; do { seconds = readl_relaxed(g_timerInfo.reg + FPGA_TIME_ADDR_CTRL_TIME_SECONDS); subseconds = readl_relaxed(g_timerInfo.reg + FPGA_TIME_ADDR_INFO_TIME_SUBSECONDS); seconds_check = readl_relaxed(g_timerInfo.reg + FPGA_TIME_ADDR_CTRL_TIME_SECONDS); } while (seconds != seconds_check); // 正确转换:1秒 = 100,000,000个10ns tick counter = (u64)seconds * 100000000ULL; counter += subseconds; return counter; }
核心问题2:时间转换逻辑错误
你的代码中subseconds << 1是完全错误的:FPGA的亚秒寄存器以10纳秒步长递增,而你注册的clocksource频率是100MHz(即每个tick对应10纳秒),因此亚秒寄存器的值直接就是低32位的tick数,秒寄存器需要转换为对应的tick数(1秒=100,000,000 ticks),组合后才是正确的计数器值。之前的位运算会导致时间计算严重偏差。
核心问题3:Sched_clock注册不当
你使用CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK宏来注册调度器时钟,但这个宏是针对ARM全局定时器的专用配置,不适用于你的FPGA时钟。此外,sched_clock要求读取操作极高效,若原子性读取逻辑未优化,可能引发额外的panic。
修复方法:
- 移除依赖
CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK的代码块 - 若需要注册调度器时钟,使用通用的
sched_clock_register接口,但确保读取函数足够高效(比如使用汇编优化或确保寄存器访问无延迟)
核心问题4:驱动框架不规范
你直接使用of_iomap和全局变量的方式初始化,未遵循Linux平台驱动的标准框架,可能导致初始化时机错误或资源泄漏。
修复方法:改为标准platform_driver结构,示例如下:
static struct platform_driver fpga_time_driver = { .driver = { .name = FPGA_TIME_DRIVER_NAME, .of_match_table = fpga_time_of_match, }, }; static const struct of_device_id fpga_time_of_match[] = { { .compatible = "your,fpga-time" }, { }, }; MODULE_DEVICE_TABLE(of, fpga_time_of_match); module_platform_driver_probe(fpga_time_driver, FPGA_TIME_probe);
在probe函数中完成iomap和clocksource注册:
static int FPGA_TIME_probe(struct platform_device *pdev) { struct resource *res; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); g_timerInfo.reg = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(g_timerInfo.reg)) return PTR_ERR(g_timerInfo.reg); return timer_clocksource_init(); }
用户态POSIX API访问说明
当clocksource正确注册并成为系统首选时钟源(调整FPGA_TIME_TIMER_RATING为300以上,高于默认时钟源的评级),内核的时间系统会自动基于该时钟运行。用户态应用直接使用标准POSIX API(如clock_gettime(CLOCK_REALTIME, ...)、gettimeofday())即可获取同步后的时间,无需直接访问硬件寄存器。
若需要将FPGA时钟作为系统实时时钟(RTC)使用,还可以额外注册一个RTC驱动,将FPGA的秒/亚秒寄存器映射为RTC时间,进一步完善系统时间的持久化和同步机制。
内容的提问来源于stack exchange,提问作者Ryan Sherlock

