Linux的移植(1)
ARM Linux底层驱动的组成和现状 为了让Linux在一个全新的ARM SoC上跑起来,需要提供大量的底层支持,如定时器节拍、中断控制器、SMP启动、CPU热插拔以及底层的GPIO、时钟、pinctrl和DMA硬件的封装等。
定时器节拍为Linux基于时间片的调度机制以及内核和用户空间的定时器提供支持,中断控制器的驱动则使得Linux内核工程师可以直接调用local_irq_disable()、local_irq_enable()等通用的中断API,而SMP启动支持则用于让SoC内部的多个CPU核都投入运行,CPU热插拔则运行运行时挂载或拔除CPU。
内核节拍 一般将linux做移植时,会从相应SoC上找一个定时器,并将该定时器配置为HZ的频率,在每个时钟节拍到来时,调用ARM Linux内核核心层的timer_tick()函数,从而引发系统里的一系列行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static irqreturn_t ebsa110_timer_interrupt(int irq, void *dev_id) { u32 count; /* latch and read timer 1 */ __raw_writeb(0x40, PIT_CTRL); count = __raw_readb(PIT_T1); count |= __raw_readb(PIT_T1) << 8; count += COUNT; __raw_writeb(count & 0xff, PIT_T1); __raw_writeb(count >> 8, PIT_T1); timer_tick(); return IRQ_HANDLED; } static struct irqaction ebsa110_timer_irq = { .name = "EBSA110 Timer Tick", .flags = IRQF_TIMER | IRQF_IRQPOLL, .handler = ebsa110_timer_interrupt, }; /* * Set up timer interrupt. */ void __init ebsa110_timer_init(void) { arch_gettimeoffset = ebsa110_gettimeoffset; /* * Timer 1, mode 2, LSB/MSB */ __raw_writeb(0x70, PIT_CTRL); __raw_writeb(COUNT & 0xff, PIT_T1); __raw_writeb(COUNT >> 8, PIT_T1); setup_irq(IRQ_EBSA110_TIMER0, &ebsa110_timer_irq); }
当前的Linux大多采用无节拍方案,并支持高精度定时器,内核的配置一般会使能NO_HZ和HIGH_RES_TIMERS。无节拍并不是说系统中没有时钟节拍,而是说这个节拍不再像如上那样周期性产生。无节拍意味着,根据系统的运行情况,以事件驱动的方式动态决定下一个节拍在何时发生,其定时器中断发生的时间间隔可长可短。
而SoC底层的定时器也被实现为一个clock_event_device和clocksource形式的驱动。而在定时器中断服务程序中,也不再调用timer_tick(),而是调用clock_event_device的event_handler()成员函数。具体可参考一个典型的新内核定时器驱动 。
如下为几个总要接口函数的解释:
clock_event_device->set_next_event:该接口的第一个参数是linux内核传递给底层定时器的一个差值,表示在多少个计数的时刻产生下一次节拍的中断。 clocksorce->read:该接口可读取出从开机到当前时刻定时器计数器已经走过的值,无论有没有设置当计数器达到某值时产生中断。因此该接口给内核提供了一个底层的准确的参考时间。 timer_interrupt:中断服务程序直接调用clock_event_device->event_handler()
对于多核处理器的定时器分配来说,通常有如下两种实现方式:
每个核分配一个独立的定时器,各个核根据自身的运行情况动态地设置自己时钟中断发生的时刻。
只给CPU0提供定时器,由CPU0将定时器中断通过IPI(处理器间中断)广播到其它核。参考arch/arm/kernel/smp.c:IPI_TIMER
中断控制器 对于移植linux来说,必须要提供内核中断相关API接口的底层支持。拿local_irq_disable()、local_irq_enable()接口来说,在内核层面它们是与硬件无关的,但具体到硬件相关的实现时,通常的做法是直接让CPU本身不响应中断请求。而disable_irq()、enable_irq()则与之相反,它们的实现针对的是中断控制器。因此,关于中断屏蔽的实现可分为如下三个级别。
对于中断控制器方便的移植工作,主要是实现为irq_chip驱动的形式,其具体实现就是对irq_chip中定义的各个接口进行填充。irq_chip是内核中对于中断控制器的描述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 struct irq_chip { struct device *parent_device; const char *name; unsigned int (*irq_startup)(struct irq_data *data); void (*irq_shutdown)(struct irq_data *data); void (*irq_enable)(struct irq_data *data); void (*irq_disable)(struct irq_data *data); void (*irq_ack)(struct irq_data *data); void (*irq_mask)(struct irq_data *data); void (*irq_mask_ack)(struct irq_data *data); void (*irq_unmask)(struct irq_data *data); void (*irq_eoi)(struct irq_data *data); int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force); int (*irq_retrigger)(struct irq_data *data); int (*irq_set_type)(struct irq_data *data, unsigned int flow_type); int (*irq_set_wake)(struct irq_data *data, unsigned int on); void (*irq_bus_lock)(struct irq_data *data); void (*irq_bus_sync_unlock)(struct irq_data *data); void (*irq_cpu_online)(struct irq_data *data); void (*irq_cpu_offline)(struct irq_data *data); void (*irq_suspend)(struct irq_data *data); void (*irq_resume)(struct irq_data *data); void (*irq_pm_shutdown)(struct irq_data *data); void (*irq_calc_mask)(struct irq_data *data); void (*irq_print_chip)(struct irq_data *data, struct seq_file *p); int (*irq_request_resources)(struct irq_data *data); void (*irq_release_resources)(struct irq_data *data); void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg); void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg); int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state); int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state); int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info); void (*ipi_send_single)(struct irq_data *data, unsigned int cpu); void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest); int (*irq_nmi_setup)(struct irq_data *data); void (*irq_nmi_teardown)(struct irq_data *data); unsigned long flags; };
在实际芯片中,中断控制器可能不止一个,多个中断控制器之间还很可能是级联的。如下图所示:
假设如上图中芯片内部有一个中断控制器,支持32个中断源,28~31号中断源来自于GPIO控制器外围的4组GPIO,每组GPIO上又有32个中断。
因此gpio0_0~gpio0_31这些引脚共用第一级中的28号中断。而这些引脚本身的中断号在实现于GPIO控制器对应的irq_chip驱动时,可以将其映射到linux系统的32~63号中断。同理,其它3组GPIO中断也可进行同样的映射。
但是在实际中,这种多级中断之间可能不一定是一种简单的线性映射。因此,Linux使用IRQ Domain来描述一个中断控制器所管理的中断源。可通过irq_domain*类接口在中断源于irq_desc之间建立映射关系,其实质就是针对这个IRQ Domain维护了一个hwirq和逻辑IRQ之间关系的一个表或其它查找数据结构。
实际上在内核中,中断号更多是一个逻辑概念,更多的是关心在设备树中设置正确的interrupt_parrent和相对其的偏移。当然对于这种级联式的中断控制器,可参考/drivers/pinctrl/sirf/pinctrl-sirf.c中的irq_chip部分的实现。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com