为了让 Linux 在一个全新的 ARM SoC 上运行,需要提供大量的底层支撑,如定时器节拍、中断控制器、 SMP 启动、 CPU 热插拔以及底层的 GPIO 、时钟、 pinctrl 和 DMA 硬件的封装等。 定时器节拍、中断控制器、 SMP 启动和CPU 热插拔这几部分相对来说没有像早期 GPIO 、时钟、 pinctrl 和 DMA 的实现那么杂乱,基本上有个固定的套路。定时器节拍为 Linux 基于时间片的调度机制以及内核和用户空间的定时器提供支撑,中断控制器的驱动则使得 Linux 内核的工程师可以直接调用 local_irq_disable ()、 disable_irq ()等通用的中断 API ,而 SMP 启动支持则用于让 SoC 内部的多个 CPU 核都投入运行, CPU 热插拔则运行运行时挂载或拔除 CPU 。这些工作,在 Linux 3.0 之后的内核中, Linux 社区对比逐步进行了良好的层次划分和架构设计。 在 GPIO 、时钟、 pinctrl 和 DMA 驱动方面,在 Linux 2.6 时代,内核已或多或少有 GPIO 、时钟等底层驱动的架构,但是核心层的代码太薄弱,各 SoC 在这些基础设施实现方面存在巨大差异,而且每个 SoC 仍然需要实现大量的代码。pinctrl 和 DMA 则最为混乱,几乎各家公司都定义了自己独特的实现和 API 。 社区必须改变这种局面,于是 Linux 社区在 2011 年后进行了如下工作,这些工作在目前的 Linux 内核中基本准备就绪: ·STEricsson 公司的工程师 Linus Walleij 提供了新的 pinctrl 驱动架构,内核中新增加一个drivers/pinctrl 目录,支撑SoC 上的引脚复用,各个 SoC 的实现代码统一放入该目录。 ·ti 公司的工程师 Mike Turquette 提供了通过时钟框架,让具体 SoC 实现 clk_ops ()成员函数,并通过clk_register ()、 clk_register_clkdev ()注册时钟源以及源与设备的对应关系,具体的时钟驱动都统一迁移到drivers/clk 目录中。 · 建议各 SoC 统一采用 dma engine 架构实现 DMA 驱动,该架构提供了通用的 DMA 通道 API ,如dma engine_prep_slave_single ()、 dma engine_submit ()等,要求 SoC 实现 dma_device 的成员函数,实现代码统一放入 drivers/dma 目录中。 · 在 GPIO 方面, drivers/gpio 下的 gpiolib 已能与新的 pinctrl 完美共存,实现引脚的 GPIO 和其他功能之间的复用,具体的 SoC 只需实现通用的 gpio_chip 结构体的成员函数。 经过以上工作,基本上就把芯片底层基础架构方面的驱动架构统一了,实现方法也统一了。另外,目前 GPIO 、时钟、 pinmux 等都能良好地进行设备树的映射处理,譬如我们可以方便地在 .dts 中定义一个设备要的时钟、pinmux 引脚以及 GPIO 。 除了上述基础设施以外,在将 Linux 移植入新的 SoC 过程中,工程师常常强烈依赖于早期的 printk 功能,内核则提供了相关的 DEBUG_LL 和 EARLY_PRINTK 支持,只需要 SoC 提供商实现少量的回调函数或宏。 内核节拍驱动 Linux 2.6 的早期( Linux2.6.21 之前)内核是基于节拍设计的,一般 SoC 公司在将 Linux 移植到自己芯片上的时候,会从芯片内部找一个定时器,并将该定时器配置为赫兹的频率,在每个时钟节拍到来时,调用 ARM Linux 内核核心层的 timer_tick ()函数,从而引发系统里的一系列行为。如 Linux 2.6.17 中 arch/arm/mach-s3c2410/time.c 的做法类似于代码清单所示: 代码清单 20.1 早期内核的节拍驱动 /* * IRQ handler for the timer */ static irqreturn_t s3c2410_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) {
write_seqlock(&xtime_lock); timer_tick(regs); write_sequnlock(&xtime_lock); return IRQ_HANDLED; } static struct irqaction s3c2410_timer_irq = {
.name = "S3C2410 Timer Tick", .flags = SA_INTERRUPT | SA_TIMER, .handler = s3c2410_timer_interrupt, }; static void __init s3c2410_timer_init (void) {
s3c2410_timer_setup(); setup_irq(IRQ_TIMER4, &s3c2410_timer_irq); } 将硬件的 TIMER4 定时器配置为周期触发中断,每个中断到来就会自动调用内核函数 timer_tick ()。 当前 Linux 多采用无节拍方案,并支持高精度定时器,内核的配置一般会使能 NO_HZ (即无节拍,或者说动态节拍)和 HIGH_RES_TIMERS 。要强调的是无节拍并不是说系统中没有时钟节拍,而是说这个节拍不再像以前那样周期性地产生。无节拍意味着,根据系统的运行情况,以事件驱动的方式动态决定下一个节拍在何时发生。 如果画一个时间轴,周期节拍的系统节拍中断发生的时序如图所示:
在当前的 Linux 系统中, SoC 底层的定时器被实现为一个 clock_event_device 和 clocksource 形