Linux的移植(1)

  1. ARM Linux底层驱动的组成和现状
  2. 内核节拍
  3. 中断控制器

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()

对于多核处理器的定时器分配来说,通常有如下两种实现方式:

  1. 每个核分配一个独立的定时器,各个核根据自身的运行情况动态地设置自己时钟中断发生的时刻。
  2. 只给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;
};

在实际芯片中,中断控制器可能不止一个,多个中断控制器之间还很可能是级联的。如下图所示:

SoC中断控制器分布示例

假设如上图中芯片内部有一个中断控制器,支持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

文章标题:Linux的移植(1)

本文作者:红尘追风

发布时间:2016-12-03, 20:31:12

原始链接:http://www.micernel.com/2016/12/03/Linux%E7%9A%84%E7%A7%BB%E6%A4%8D(1)/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录