- UID
- 1029342
- 性别
- 男
|
(c)step (b)中获取了8个bit的cpu mask值,通过简单的copy,扩充为32个bit,每8个bit都是cpu mask的值,这么做是为了下一步设定所有IRQ(对于GIC而言就是SPI类型的中断)的CPU mask。
(d)设定每个SPI类型的中断都是只送达该CPU。
(e)配置GIC distributor的其他寄存器,代码如下:
void __init gic_dist_config(void __iomem *base, int gic_irqs, void (*sync_access)(void))
{
unsigned int i;
/* Set all global interrupts to be level triggered, active low. */
for (i = 32; i < gic_irqs; i += 16)
writel_relaxed(0, base + GIC_DIST_CONFIG + i / 4);
/* Set priority on all global interrupts. */
for (i = 32; i < gic_irqs; i += 4)
writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i);
/* Disable all interrupts. Leave the PPI and SGIs alone as they are enabled by redistributor registers. */
for (i = 32; i < gic_irqs; i += 32)
writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i / 8);
if (sync_access)
sync_access();
}
程序的注释已经非常清楚了,这里就不细述了。需要注意的是:这里设定的都是缺省值,实际上,在各种driver的初始化过程中,还是有可能改动这些设置的(例如触发方式)。
(2)CPU interface初始化,代码如下:
static void gic_cpu_init(struct gic_chip_data *gic)
{
void __iomem *dist_base = gic_data_dist_base(gic);-------Distributor的基地址空间
void __iomem *base = gic_data_cpu_base(gic);-------CPU interface的基地址空间
unsigned int cpu_mask, cpu = smp_processor_id();------获取CPU的逻辑ID
int i;
cpu_mask = gic_get_cpumask(gic);-------------(a)
gic_cpu_map[cpu] = cpu_mask;
for (i = 0; i < NR_GIC_CPU_IF; i++)
if (i != cpu)
gic_cpu_map &= ~cpu_mask; ------------(b)
gic_cpu_config(dist_base, NULL); --------------(c)
writel_relaxed(0xf0, base + GIC_CPU_PRIMASK);-------(d)
writel_relaxed(1, base + GIC_CPU_CTRL);-----------(e)
}
(a)系统软件实际上是使用CPU 逻辑ID这个概念的,通过smp_processor_id可以获得本CPU的逻辑ID。gic_cpu_map这个全部lookup table就是用CPU 逻辑ID作为所以,去寻找其cpu mask,后续通过cpu mask值来控制中断是否送达该CPU。在gic_init_bases函数中,我们将该lookup table中的值都初始化为0xff,也就是说不进行mask,送达所有的CPU。这里,我们会进行重新修正。
(b)清除lookup table中其他entry中本cpu mask的那个bit。
(c)设定SGI和PPI的初始值。具体代码如下:
void gic_cpu_config(void __iomem *base, void (*sync_access)(void))
{
int i;
/* Deal with the banked PPI and SGI interrupts - disable all
* PPI interrupts, ensure all SGI interrupts are enabled. */
writel_relaxed(0xffff0000, base + GIC_DIST_ENABLE_CLEAR);
writel_relaxed(0x0000ffff, base + GIC_DIST_ENABLE_SET);
/* Set priority on PPI and SGI interrupts */
for (i = 0; i < 32; i += 4)
writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);
if (sync_access)
sync_access();
}
程序的注释已经非常清楚了,这里就不细述了。
(d)通过Distributor中的寄存器可以控制送达CPU interface,中断来到了GIC的CPU interface是否可以真正送达CPU呢?也不一定,还有一道关卡,也就是CPU interface中的Interrupt Priority Mask Register。这个寄存器设定了一个中断优先级的值,只有中断优先级高过该值的中断请求才会被送到CPU上去。我们在前面初始化的时候,给每个interrupt ID设定的缺省优先级是0xa0,这里设定的priority filter的优先级值是0xf0。数值越小,优先级越过。因此,这样的设定就是让所有的interrupt source都可以送达CPU,在CPU interface这里不做控制了。
(e)设定CPU interface的control register。enable了group 0的中断,disable了group 1的中断,group 0的interrupt source触发IRQ中断(而不是FIQ中断)。
(3)GIC电源管理初始化,代码如下:
static void __init gic_pm_init(struct gic_chip_data *gic)
{
gic->saved_ppi_enable = __alloc_percpu(DIV_ROUND_UP(32, 32) * 4, sizeof(u32));
gic->saved_ppi_conf = __alloc_percpu(DIV_ROUND_UP(32, 16) * 4, sizeof(u32));
if (gic == &gic_data[0])
cpu_pm_register_notifier(&gic_notifier_block);
}
这段代码前面主要是分配两个per cpu的内存。这些内存在系统进入sleep状态的时候保存PPI的寄存器状态信息,在resume的时候,写回寄存器。对于root GIC,需要注册一个和电源管理的事件通知callback函数。不得不吐槽一下gic_notifier_block和gic_notifier这两个符号的命名,看不出来和电源管理有任何关系。更优雅的名字应该包括pm这样的符号,以便让其他工程师看到名字就立刻知道是和电源管理相关的。
四、GIC callback函数分析
1、irq domain相关callback函数分析
irq domain相关callback函数包括:
(1)gic_irq_domain_map函数:创建IRQ number和GIC hw interrupt ID之间映射关系的时候,需要调用该回调函数。具体代码如下:
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
if (hw < 32) {------------------SGI或者PPI
irq_set_percpu_devid(irq);--------------------------(a)
irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq);-------(b)
set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);--------------(c)
} else {
irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);----------(d)
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
gic_routable_irq_domain_ops->map(d, irq, hw);----------------(e)
}
irq_set_chip_data(irq, d->host_data);-----设定irq chip的私有数据
return 0;
}
(a)SGI或者PPI和SPI最大的不同是per cpu的,SPI是所有CPU共享的,因此需要分配per cpu的内存,设定一些per cpu的flag。
(b)设定该中断描述符的irq chip和high level的handler
(c)设定irq flag是有效的(因为已经设定好了chip和handler了),并且request后不是auto enable的。
(d)对于SPI,设定的high level irq event handler是handle_fasteoi_irq。对于SPI,是可以probe,并且request后是auto enable的。
(e)有些SOC会在各种外设中断和GIC之间增加cross bar(例如TI的OMAP芯片),这里是为那些ARM SOC准备的
(2)gic_irq_domain_unmap是gic_irq_domain_map的逆过程也就是解除IRQ number和GIC hw interrupt ID之间映射关系的时候,需要调用该回调函数。
(3)gic_irq_domain_xlate函数:除了标准的属性之外,各个具体的interrupt controller可以定义自己的device binding。这些device bindings都需在irq chip driver这个层面进行解析。要给定某个外设的device tree node 和interrupt specifier,该函数可以解码出该设备使用的hw interrupt ID和linux irq type value 。具体的代码如下:
static int gic_irq_domain_xlate(struct irq_domain *d,
struct device_node *controller,
const u32 *intspec, unsigned int intsize,--------输入参数
unsigned long *out_hwirq, unsigned int *out_type)----输出参数
{
unsigned long ret = 0;
*out_hwirq = intspec[1] + 16; ---------------------(a)
*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; -----------(b)
return ret;
}
(a)根据gic binding文档的描述,其interrupt specifier包括3个cell,分别是interrupt type(0 表示SPI,1表示PPI),interrupt number(对于PPI,范围是[0-15],对于SPI,范围是[0-987]),interrupt flag(触发方式)。GIC interrupt specifier中的interrupt number需要加上16(也就是加上SGI的那些ID号),才能转换成GIC的HW interrupt ID。
(b)取出bits[3:0]的信息,这些bits保存了触发方式的信息 |
|