Linux的移植(2)

  1. SMP多核启动及CPU热插拔驱动
    1. 多核启动
    2. CPU热插拔
  2. GPIO与pinctrl
    1. pinctrl和引脚
    2. 引脚组(Pin Group)
    3. 引脚配置
    4. pinctrl子系统与GPIO子系统交互
    5. 引脚复用

SMP多核启动及CPU热插拔驱动

多核启动

linux中对ARM芯片而言,在bootrom代码中,每个CPU都会识别自身的ID,如果ID是0,则引导Bootloader和Linux内核执行,如果不是0,则Bootrom一般在上电时将自身置于WFI或WFE状态,并等待CPU0给其发CPU核间中断或时间来唤醒它。一个典型的多核Linux启动流程如下。

多核Linux启动

CPU0唤醒其它CPU的动作在内核中被封装为一个smp_operations结构体,对于ARM而言,它定义于arch/arm/include/asm/smp.h中。

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
struct smp_operations {
#ifdef CONFIG_SMP
/*
* Setup the set of possible CPUs (via set_cpu_possible)
*/
void (*smp_init_cpus)(void);
/*
* Initialize cpu_possible map, and enable coherency
*/
void (*smp_prepare_cpus)(unsigned int max_cpus);

/*
* Perform platform specific initialisation of the specified CPU.
*/
void (*smp_secondary_init)(unsigned int cpu);
/*
* Boot a secondary CPU, and assign it the specified idle task.
* This also gives us the initial stack to use for this CPU.
*/
int (*smp_boot_secondary)(unsigned int cpu, struct task_struct *idle);
#ifdef CONFIG_HOTPLUG_CPU
int (*cpu_kill)(unsigned int cpu);
void (*cpu_die)(unsigned int cpu);
bool (*cpu_can_disable)(unsigned int cpu);
int (*cpu_disable)(unsigned int cpu);
#endif
#endif
};

而linux中硬件相关的代码通过实现smp_operations接口来对启动过程的底层实现进行填充,其实现类似下面代码所示。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
bool __init vexpress_smp_init_ops(void)
{
#ifdef CONFIG_MCPM
int cpu;
struct device_node *cpu_node, *cci_node;

/*
* The best way to detect a multi-cluster configuration
* is to detect if the kernel can take over CCI ports
* control. Loop over possible CPUs and check if CCI
* port control is available.
* Override the default vexpress_smp_ops if so.
*/
for_each_possible_cpu(cpu) {
bool available;

cpu_node = of_get_cpu_node(cpu, NULL);
if (WARN(!cpu_node, "Missing cpu device node!"))
return false;

cci_node = of_parse_phandle(cpu_node, "cci-control-port", 0);
available = cci_node && of_device_is_available(cci_node);
of_node_put(cci_node);
of_node_put(cpu_node);

if (!available)
return false;
}

mcpm_smp_set_ops();
return true;
#else
return false;
#endif
}

static const struct of_device_id vexpress_smp_dt_scu_match[] __initconst = {
{ .compatible = "arm,cortex-a5-scu", },
{ .compatible = "arm,cortex-a9-scu", },
{}
};

static void __init vexpress_smp_dt_prepare_cpus(unsigned int max_cpus)
{
struct device_node *scu = of_find_matching_node(NULL,
vexpress_smp_dt_scu_match);

if (scu)
scu_enable(of_iomap(scu, 0));

/*
* Write the address of secondary startup into the
* system-wide flags register. The boot monitor waits
* until it receives a soft interrupt, and then the
* secondary CPU branches to this address.
*/
vexpress_flags_set(__pa_symbol(versatile_secondary_startup));
}

#ifdef CONFIG_HOTPLUG_CPU
static void vexpress_cpu_die(unsigned int cpu)
{
versatile_immitation_cpu_die(cpu, 0x40);
}
#endif

const struct smp_operations vexpress_smp_dt_ops __initconst = {
.smp_prepare_cpus = vexpress_smp_dt_prepare_cpus,
.smp_secondary_init = versatile_secondary_init,
.smp_boot_secondary = versatile_boot_secondary,
#ifdef CONFIG_HOTPLUG_CPU
.cpu_die = vexpress_cpu_die,
#endif
};


/* SoC定义 */
static const char * const v2m_dt_match[] __initconst = {
"arm,vexpress",
NULL,
};

DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
.dt_compat = v2m_dt_match,
.l2c_aux_val = 0x00400000,
.l2c_aux_mask = 0xfe0fffff,
.smp = smp_ops(vexpress_smp_dt_ops),
.smp_init = smp_init_ops(vexpress_smp_init_ops),
MACHINE_END

如上代码的SMP唤醒流程如下所示:

CPU0唤醒其它CPU

CPU热插拔

CPU热插拔的实现也是与芯片密切相关的。对VEXPRESS而言,其实现了smp_operations的cpu_die成员来实现的CPU热插拔。xxx_cpu_die()会在进行CPUn的拔除操作时将CPUn投入低功耗的WFI状态,其代码如下所示。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
static inline void versatile_immitation_enter_lowpower(unsigned int actrl_mask)
{
unsigned int v;

asm volatile(
"mcr p15, 0, %1, c7, c5, 0\n"
" mcr p15, 0, %1, c7, c10, 4\n"
/*
* Turn off coherency
*/
" mrc p15, 0, %0, c1, c0, 1\n"
" bic %0, %0, %3\n"
" mcr p15, 0, %0, c1, c0, 1\n"
" mrc p15, 0, %0, c1, c0, 0\n"
" bic %0, %0, %2\n"
" mcr p15, 0, %0, c1, c0, 0\n"
: "=&r" (v)
: "r" (0), "Ir" (CR_C), "Ir" (actrl_mask)
: "cc");
}

static inline void versatile_immitation_leave_lowpower(unsigned int actrl_mask)
{
unsigned int v;

asm volatile(
"mrc p15, 0, %0, c1, c0, 0\n"
" orr %0, %0, %1\n"
" mcr p15, 0, %0, c1, c0, 0\n"
" mrc p15, 0, %0, c1, c0, 1\n"
" orr %0, %0, %2\n"
" mcr p15, 0, %0, c1, c0, 1\n"
: "=&r" (v)
: "Ir" (CR_C), "Ir" (actrl_mask)
: "cc");
}

static inline void versatile_immitation_do_lowpower(unsigned int cpu, int *spurious)
{
/*
* there is no power-control hardware on this platform, so all
* we can do is put the core into WFI; this is safe as the calling
* code will have already disabled interrupts.
*
* This code should not be used outside Versatile platforms.
*/
for (;;) {
wfi();

if (versatile_cpu_release == cpu_logical_map(cpu)) {
/*
* OK, proper wakeup, we're done
*/
break;
}

/*
* Getting here, means that we have come out of WFI without
* having been woken up - this shouldn't happen
*
* Just note it happening - when we're woken, we can report
* its occurrence.
*/
(*spurious)++;
}
}

/*
* platform-specific code to shutdown a CPU.
* This code supports immitation-style CPU hotplug for Versatile/Realview/
* Versatile Express platforms that are unable to do real CPU hotplug.
*/
void versatile_immitation_cpu_die(unsigned int cpu, unsigned int actrl_mask)
{
int spurious = 0;

versatile_immitation_enter_lowpower(actrl_mask);
versatile_immitation_do_lowpower(cpu, &spurious);
versatile_immitation_leave_lowpower(actrl_mask);

if (spurious)
pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious);
}

其睡眠与wfi(),当CPUn再次在线的时候,又会因为CPU0给它发出的IPI而从wfi()函数返回继续执行,醒来后就做“versatile_cpu_release == cpu_logical_map(cpu)”的判断,以确定该次醒来是否是由CPU0进行的一次正常唤醒。

GPIO与pinctrl

在drivers/gpio下实现了通用的基于gpiolib的GPIO驱动,其中定义了一个通用的描述底层GPIO控制器的gpio_chip结构,并要求具体的SoC实现gpio_chip结构体的成员函数,最后通过gpiochip_add()注册gpio_chip。但在GPIO兼有多种功能且需要复杂配置的情况下,GPIO驱动部分往往一到drivers/pinctrl下与pinmux一起实现。

在许多SoC内部都包含pin控制器,通过pin控制器的寄存器,我们可以配置一个或多个引脚的功能和特性。而pinctrl驱动则是内核定义的用于pin控制器操作的软件接口。其功能实现主要包含:

  1. 枚举且命名pin控制器可控制的所有引脚
  2. 提供引脚复用的能力
  3. 提供配置引脚的能力,如驱动能力、上拉下拉、开漏等

pinctrl和引脚

在pinctrl驱动中,我们需要定义引脚。假设一个PGA封装的芯片引脚布局如下所示。

PGA封装

则在pinctrl驱动初始化的时候,需要像pinctrl子系统注册一个pinctrl_desc描述符,该描述符的pins成员中包含所有引脚的列表,这在pinctrl初始化时填充。

引脚组(Pin Group)

在pinctrl子系统中,支持将一组引脚绑定为同一功能。如{0,8,16,24}这组引脚为SPI功能,{24,25}这组引脚为I2C功能。在驱动中,要实现这种分组只需实现pinctrl_ops相关的接口,并将其填充到pinctr_desc中去。示例可参考drivers/pinctrl/pinctrl-pic32.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static const struct pinctrl_ops pic32_pinctrl_ops = {
.get_groups_count = pic32_pinctrl_get_groups_count,
.get_group_name = pic32_pinctrl_get_group_name,
.get_group_pins = pic32_pinctrl_get_group_pins,
.dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
.dt_free_map = pinctrl_utils_free_map,
};

static struct pinctrl_desc pic32_pinctrl_desc = {
.name = "pic32-pinctrl",
.pctlops = &pic32_pinctrl_ops,
.pmxops = &pic32_pinmux_ops,
.confops = &pic32_pinconf_ops,
.owner = THIS_MODULE,
};

引脚配置

设备驱动有时需要配置引脚,比如可能把引脚设置为高阻或者三态,或通过某阻值将引脚上拉/下拉以确保默认状态下引脚的电平状态。如下某设备驱动中将某引脚上拉:

1
2
#include <linux/pinctrl/consumer.h>
ret = pin_config_set("foo-dev", "FOO_GPIO_PIN", PLATFORM_X_PULL_UP);

PLATFORM_X_PULL_UP由特定pinctrl驱动定义,在驱动中需要完成这些配置所需要的回调函数,即pinctrl_desc的confops成员函数。

pinctrl子系统与GPIO子系统交互

pinctrl驱动所覆盖的引脚可同时作为GPIO用,内核的GPIO子系统和pinctrl子系统是并行工作的,但有时需要交叉映射,在这种时候,pinctrl驱动需要告知pinctrl子系统核心层GPIO与底层pinctrl驱动所管理的引脚之间的映射关系。

比如pinctrl驱动定义的32~47号引脚与gpio_chip实例chip_a的GPIO对应,64~71号引脚与gpio_chip实例chip_b的GPIO对应,则其映射关系为:

1
2
3
4
5
6
chip a:
- GPIO range : [32 .. 47]
- pin range. : [32 .. 47]
chip b:
- GPIO range : [48 .. 55]
- pin range : [64 .. 71]

对于这种映射关系在内核中可以使用pinctrl_gpio_range结构体来进行描述,而在pinctrl驱动中,则通过pinctrl_add_gpio_range()接口向pinctrl子系统核心层注册这种映射关系。

而在基于gpiolib的GPIO驱动中,GPIO的申请与释放实质上也是通过pinctrl子系统来完成的,因为其中管理着两种不同驱动间GPIO资源在软件上的映射关系。

引脚复用

一个特定的功能总是要求由一组引脚来完成,一组引脚的数量可以是一个或多个。正如前面对引脚组的描述。

如果I2C功能由{A5, B5}引脚组成,而在定义引脚描述的pinctrl_pin_desc结构体实例的时候,将它们的序号定义为{24,25};而SPI功能由{A8,A7,A6,A5}和{G4,G3,G2,G1},即{0,8,16,24}和{38,46,54,62}两组引脚完成。

据此,功能和引脚组的组合就可以决定一组引脚在系统中的作用,因此在设置某组引脚的作用时,pinctrl核心层会将功能的序号与引脚组的序号传递给底层pinctrl驱动中的相关回调函数。

在特定pinctrl驱动中pinmux相关代码主要处理如何使能/禁止某一{功能,引脚组}的组合,例如,当spi0设备申请pinctrl0的fspi0功能和gspi0引脚组以便将gspi0引脚组配置为SPI接口时,相关的回调函数被组织进一个pinmux_ops结构体中,而该结构体的实例最终成为pinctrl_desc的pmxops成员。

具体的pinctrl、使用引脚的设备、功能、引脚组的映射关系,可以在板文件中通过定义pinctrl_map结构体实例来实现。当然这种映射关系最好是在设备树中通过节点的属性进行,具体属性依赖于具体pinctrl驱动的实现,在pinctrl驱动中通过pinctrl_ops结构体的dt_node_to_map()成员函数读出属性建立映射表。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

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

本文作者:红尘追风

发布时间:2016-12-16, 20:41:10

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

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

目录