为你的开发板定制 Linux 系统
1. 移植 Linux 到新开发板
在将 Linux 移植到新开发板时,可采用类似移植 U - Boot 的方法,即找到与新开发板最匹配的配置并借鉴。这里假设所选 CPU 已在 Linux 内核中得到支持,因为移植到新 CPU 挑战较大。
本次选择将 Linux 移植到基于飞思卡尔 MPC5200 32 位嵌入式 PowerPC 处理器的定制控制器板,名为 PowerDNA Controller。通过查看最新 Linux 版本的默认配置,找到包含 MPC5200 CPU 的配置作为基线。该开发板具有简单的框图,包含板载闪存、动态 RAM、串口和各种 I/O 设备,大部分集成在 MPC5200 处理器中。
2. 移植前提和假设
当 Linux 内核从引导加载程序获取控制权时,有一些基本假设:
-
DRAM 控制器初始化
:引导加载程序必须初始化 DRAM 控制器,因为 Linux 不参与芯片级 SDRAM 控制器的设置,它假定系统 RAM 存在且功能正常。PowerDNA Controller 中的 U - Boot 引导加载程序已完成 CPU、DRAM 和其他系统最低运行所需硬件的初始化。
-
系统内存映射初始化
:引导加载程序应初始化系统内存映射,通常通过一组处理器寄存器定义给定内存地址范围内哪些片选信号有效。
-
串口配置
:在某些开发板上,内核假定串口已配置,这样在自身串口驱动安装之前就能将早期内核启动消息显示到串口。一些架构和硬件平台包含如
*_serial_putc()
这样的函数,可向引导加载程序或早期内核设置代码预配置的串口发送字符串。
总之,将 Linux 移植到新开发板的基本前提是引导加载程序已移植并安装到开发板,且完成了特定于开发板的底层硬件初始化。对于 Linux 有直接设备驱动支持的设备,如以太网控制器或 I2C 控制器,无需提前初始化,内核会处理这些。
为与自己开发板最接近的开发板配置和构建 Linux 内核是个好主意,这能提供一个已知良好的起点,即一个为开发板配置且能无错误编译的 Linux 内核源代码树。编译 Linux 2.6 内核的命令如下:
$ make ARCH=ppc CROSS_COMPILE=ppc_82xx- uImage
此命令行将生成与 U - Boot 引导加载程序兼容的 Linux 可引导镜像,
uImage
目标指定了这一点。
3. 定制内核初始化
有了基线内核源代码树后,需确定从何处开始为特定开发板进行定制。对于 PowerPC 架构,特定于开发板的文件位于
.../arch/ppc/platforms
目录。在该目录中找到
lite5200.c
文件,它包含两个数据结构和五个函数:
lite5200_show_cpuinfo() /* Prints user specified text string */
lite5200_map_irq() /* Sets h/w specific INT logic routing */
lite5200_setup_cpu() /* CPU specific initialization */
lite5200_setup_arch() /* Arch. specific initialization */
platform_init() /* Machine or board specific init */
下面看看这些函数的使用方式。上电时,引导加载程序将控制权传递给内核的引导加载器,再通过内核的
head.o
模块将控制权传递给 Linux 内核,此时特定于平台的初始化开始。以下是
.../arch/ppc/kernel/head.S
中的相关代码:
...
/*
* Do early bootinfo parsing, platform-specific initialization,
* and set up the MMU.
*/
mr r3,r31
mr r4,r30
mr r5,r29
mr r6,r28
mr r7,r27
bl machine_init
bl MMU_init
...
这里可以看到汇编语言调用
machine_init
,特别重要的是寄存器
r3
到
r7
的设置,它们在引导序列早期被存储到 PowerPC 通用寄存器
r27
到
r31
中,现在从这些存储值重新加载。
machine_init()
函数在
.../arch/ppc/kernel/setup.c
文件中定义,代码如下:
void __init
machine_init(unsigned long r3, unsigned long r4, unsigned long r5,
unsigned long r6, unsigned long r7)
{
#ifdef CONFIG_CMDLINE
strlcpy(cmd_line, CONFIG_CMDLINE, sizeof(cmd_line));
#endif /* CONFIG_CMDLINE */
#ifdef CONFIG_6xx
ppc_md.power_save = ppc6xx_idle;
#endif
#ifdef CONFIG_POWER4
ppc_md.power_save = power4_idle;
#endif
platform_init(r3, r4, r5, r6, r7);
if (ppc_md.progress)
ppc_md.progress("id mach(): done", 0x200);
}
此函数有一些重要信息:
-
machine_init()
的参数代表 PowerPC 通用寄存器
r3
到
r7
,这些寄存器值未经修改传递给
platform_init()
,需要为平台修改此函数。
- 包含一些特定于机器的电源管理函数调用。如果内核配置为支持 PowerPC 6xx(
.config
文件中定义
CONFIG_6xx
),则将特定于机器的电源管理函数(
ppc6xx_idle
)的指针存储在一个结构中;如果配置为 PowerPC G5 核心(
CONFIG_POWER4
),则将其特定于机器的电源管理例程的指针存储在同一结构成员中。
4. 静态内核命令行
在
machine_init()
函数中,一个有趣的操作是存储默认内核命令行。如果内核配置中启用了
CONFIG_CMDLINE
,则可将内核命令行静态编译到内核中。操作步骤如下:
1. 在配置中启用 “Default bootloader kernel arguments”,并编辑 “Initial kernel command string”。
2. 这将在
.config
文件中生成如下条目:
...
CONFIG_CMDLINE_BOOL=y
CONFIG_CMDLINE="console=ttyS0 root=/dev/ram0 rw"
...
-
内核构建系统处理这些配置符号后,会在
.../include/linux/autoconf.h文件中生成如下条目:
...
#define CONFIG_CMDLINE_BOOL 1
#define CONFIG_CMDLINE "console=ttyS0 root=/dev/ram0 rw"
...
-
在
machine_init()函数中有如下代码:
strlcpy(cmd_line, CONFIG_CMDLINE, sizeof(cmd_line));
此内核字符串复制函数将
CONFIG_CMDLINE
定义的字符串复制到名为
cmd_line
的全局内核变量中,许多函数和设备驱动可能在引导序列早期需要检查内核命令行。全局变量
cmd_line
隐藏在
.data
段的起始处,在汇编文件
.../arch/ppc/kernel/head.S
中定义。
需要注意的是,
machine_init
汇编语言调用在
MMU_init
调用之前,这意味着从
machine_init
运行的任何代码在访问内存时支持有限。如果在调试新内核移植时遇到数据访问错误(PowerPC DSI 异常),应立即怀疑代码试图访问的内存区域未正确映射。
5. 平台初始化流程
以下是早期初始化期间的代码流程:
graph LR
A[引导加载程序/引导加载器] --> B[head.S]
B --> C[setup.c]
C --> D[.../arch/ppc/platforms/myplat.c]
在
lite5200.c
平台初始化文件中,除
platform_init()
外的所有函数都声明为静态,因此
platform_init()
是平台初始化文件的入口点。以下是
platform_init()
函数的代码:
void __init
platform_init(unsigned long r3, unsigned long r4,
unsigned long r5, unsigned long r6,
unsigned long r7)
{
/* Generic MPC52xx platform initialization */
/* TODO Create one and move a max of stuff in it.
Put this init in the syslib */
struct bi_record *bootinfo = find_bootinfo();
if (bootinfo)
parse_bootinfo(bootinfo);
else {
/* Load the bd_t board info structure */
if (r3)
memcpy((void*)&__res,(void*)(r3+KERNELBASE),
sizeof(bd_t));
#ifdef CONFIG_BLK_DEV_INITRD
/* Load the initrd */
if (r4) {
initrd_start = r4 + KERNELBASE;
initrd_end = r5 + KERNELBASE;
}
#endif
/* Load the command line */
if (r6) {
*(char *)(r7+KERNELBASE) = 0;
strcpy(cmd_line, (char *)(r6+KERNELBASE));
}
}
/* PPC Sys identification */
identify_ppc_sys_by_id(mfspr(SPRN_SVR));
/* BAT setup */
mpc52xx_set_bat();
/* No ISA bus by default */
isa_io_base = 0;
isa_mem_base = 0;
/* Powersave */
/* This is provided as an example on how to do it. But you
need to be aware that NAP disable bus snoop and that may
be required for some devices to work properly, like USB
... */
/* powersave_nap = 1; */
/* Setup the ppc_md struct */
ppc_md.setup_arch = lite5200_setup_arch;
ppc_md.show_cpuinfo = lite5200_show_cpuinfo;
ppc_md.show_percpuinfo = NULL;
ppc_md.init_IRQ = mpc52xx_init_irq;
ppc_md.get_irq = mpc52xx_get_irq;
#ifdef CONFIG_PCI
ppc_md.pci_map_irq = lite5200_map_irq;
#endif
ppc_md.find_end_of_memory = mpc52xx_find_end_of_memory;
ppc_md.setup_io_mappings = mpc52xx_map_io;
ppc_md.restart = mpc52xx_restart;
ppc_md.power_off = mpc52xx_power_off;
ppc_md.halt = mpc52xx_halt;
/* No time keeper on the LITE5200 */
ppc_md.time_init = NULL;
ppc_md.get_rtc_time = NULL;
ppc_md.set_rtc_time = NULL;
ppc_md.calibrate_decr = mpc52xx_calibrate_decr;
#ifdef CONFIG_SERIAL_TEXT_DEBUG
ppc_md.progress = mpc52xx_progress;
#endif
}
该函数包含了特定于平台的大部分定制内容:
-
查找板级特定数据
:首先查找引导加载程序提供的板级特定数据,若找到
struct bi_record
类型的数据结构,则解析这些记录并收集硬件相关数据;若未找到,则从 U - Boot 板信息结构(
bd_info
)加载数据。
-
保存初始 RAM 磁盘和内核命令行
:若内核配置了初始 RAM 磁盘(initrd),则保存其起始和结束地址;同时保存内核命令行。
-
其他初始化操作
:包括 PPC 系统标识、BAT 设置、禁用默认 ISA 总线、设置
ppc_md
结构体等。
6. 早期变量访问
假设在引导过程早期的一个代码段需要访问变量
cmd_line
,由于在早期执行时处于 1:1 实地址到物理地址映射模式,直接使用其符号访问会有问题。例如,从 Linux 内核的
System.map
文件中找到
cmd_line
的链接地址:
$ cat System.map | grep cmd_line
c0115000 D cmd_line
若在实地址 = 物理地址模式(MMU 禁用)下使用其符号访问该变量,会尝试读写大于 3GB 的地址,大多数小型嵌入式系统该区域无 RAM,可能导致未定义结果或崩溃。因此需要调整对该变量的引用,以下是
head.S
中的代码示例:
relocate_kernel:
addis r9,r26,klimit@ha /* fetch klimit */
lwz r25,klimit@l(r9)
addis r25,r25,-KERNELBASE@h
在 C 语言中,等效代码如下:
unsigned int *tmp; /* represents r25 */
tmp = *klimit;
tmp -= KERNELBASE;
总之,通过调整存储在
klimit
中的指针值到实际物理地址,就可以使用其内容。当内核启用 MMU 和虚拟寻址后,就无需担心这些问题。
7. 开发板信息结构
PowerPC 平台使用多种引导加载程序,但传递板级特定数据(如串口波特率、内存大小等)尚无统一方法。
platform_init()
函数支持两种方法:
| 方法 | 数据结构 | 说明 |
| ---- | ---- | ---- |
| 方法一 |
struct bi_record
| 搜索特殊标签识别该数据结构,若找到则解析记录并收集硬件相关数据。目前
bi_records
可包含内核命令行、initrd 镜像的起始和结束地址、机器类型和内存大小等。 |
| 方法二 |
struct bd_info
| 若未找到
bi_record
数据,PowerPC 架构期望以 U - Boot 板信息结构形式提供数据,引导加载程序负责构建该数据结构并将地址传递到寄存器 r3。 |
bi_record
结构可在
.../include/asm - ppc/bootinfo.h
中查看,
bd_info
结构可在
.../include/asm - ppc/ppcboot.h
中找到。平台初始化例程有责任利用这些必要数据完成硬件设置或传达给内核。例如:
ppc_md.find_end_of_memory = mpc52xx_find_end_of_memory;
在
mpc52xx_find_end_of_memory()
函数中:
u32 ramsize = __res.bi_memsize;
if (ramsize == 0) {
... /* Find it another way */
}
return ramsize;
这里的
__res
数据结构是引导加载程序通过寄存器 r3 传递给我们的板信息结构。
8. 机器相关调用
内核初始化或运行所需的许多常见例程依赖于架构和机器(CPU)。在
platform_init()
函数中,通过设置
ppc_md
结构体将平台特定需求传达给 Linux 内核:
/* Setup the ppc_md struct */
ppc_md.setup_arch = lite5200_setup_arch;
ppc_md.show_cpuinfo = lite5200_show_cpuinfo;
ppc_md.show_percpuinfo = NULL;
ppc_md.init_IRQ = mpc52xx_init_irq;
ppc_md.get_irq = mpc52xx_get_irq;
#ifdef CONFIG_PCI
ppc_md.pci_map_irq = lite5200_map_irq;
#endif
ppc_md.find_end_of_memory = mpc52xx_find_end_of_memory;
ppc_md.setup_io_mappings = mpc52xx_map_io;
ppc_md.restart = mpc52xx_restart;
ppc_md.power_off = mpc52xx_power_off;
ppc_md.halt = mpc52xx_halt;
ppc_md
是
struct machdep_calls
类型的全局变量,在
.../arch/ppc/kernel/setup.c
中声明。PowerPC 特定内核分支的许多地方通过该结构间接调用函数,例如:
void machine_restart(char *cmd)
{
#ifdef CONFIG_NVRAM
nvram_sync();
#endif
ppc_md.restart(cmd);
}
void machine_power_off(void)
{
#ifdef CONFIG_NVRAM
nvram_sync();
#endif
ppc_md.power_off();
}
void machine_halt(void)
{
#ifdef CONFIG_NVRAM
nvram_sync();
#endif
ppc_md.halt();
}
这些函数通过
ppc_md
结构体调用,包含了特定于机器或平台的变体。其中一些函数特定于机器,如
mpc52xx_restart
和
mpc52xx_map_io
;另一些特定于硬件平台,如
lite5200_map_irq
和
lite5200_setup_arch
。
为你的开发板定制 Linux 系统
9. 内核移植总结
在将 Linux 内核移植到基于飞思卡尔 MPC5200 的 PowerDNA Controller 开发板的过程中,我们经历了多个关键步骤。首先,要确保引导加载程序完成了必要的初始化工作,包括 DRAM 控制器和系统内存映射的初始化,以及可能的串口配置。然后,通过找到与目标开发板最匹配的内核配置作为基线,为后续的定制工作打下基础。
定制内核初始化时,我们深入研究了 PowerPC 架构下特定于开发板的文件和函数。
lite5200.c
文件中的多个函数在平台初始化过程中发挥了重要作用,特别是
platform_init()
函数,它处理了板级特定数据的加载、初始 RAM 磁盘和内核命令行的保存,以及一系列硬件相关的初始化操作。
在早期变量访问方面,由于内核在启用 MMU 之前的执行环境限制,我们需要对变量引用进行调整,以确保能够正确访问物理内存中的数据。而对于开发板信息结构的处理,提供了两种不同的数据传递方式,使得我们可以根据实际情况选择合适的方法来获取板级特定数据。
最后,通过设置
ppc_md
结构体,我们将平台特定的需求传达给了 Linux 内核,使得内核能够正确处理各种机器相关的调用,如重启、关机和中断处理等。
10. 常见问题及解决方法
在移植过程中,可能会遇到一些常见问题,以下是一些问题及相应的解决方法:
| 问题 | 现象 | 解决方法 |
| ---- | ---- | ---- |
| 数据访问错误(PowerPC DSI 异常) | 内核在引导过程中崩溃或出现未定义行为 | 检查代码是否在 MMU 启用之前尝试访问未映射的内存区域,确保对变量引用进行了正确的调整,添加必要的内存映射代码。 |
| 内核命令行未正确传递 | 内核启动时无法获取正确的参数 | 检查引导加载程序是否正确传递了内核命令行地址,确保
platform_init()
函数能够正确解析和存储命令行数据。 |
| 硬件初始化失败 | 某些硬件设备无法正常工作 | 检查
platform_init()
函数中相关的硬件初始化代码,确保设备的寄存器设置正确,必要时参考硬件手册进行调整。 |
11. 移植优化建议
为了提高移植后的内核性能和稳定性,可以考虑以下优化建议:
-
内存管理优化
:合理配置内核的内存分配策略,减少内存碎片,提高内存使用效率。可以通过调整内核参数,如
CONFIG_MEMORY_HOTPLUG
等,来实现更灵活的内存管理。
-
设备驱动优化
:对于开发板上的特定设备,如以太网控制器或 I2C 控制器,使用最新的设备驱动程序,并进行适当的优化。可以通过调整驱动程序的参数,如中断处理方式、数据传输速率等,来提高设备的性能。
-
代码优化
:对内核代码进行优化,减少不必要的函数调用和内存访问,提高代码的执行效率。可以使用编译器优化选项,如
-O2
或
-O3
,来生成更高效的机器代码。
12. 后续扩展
移植完成后,可以根据实际需求对内核进行进一步的扩展:
-
添加新的设备驱动
:如果开发板上有新的硬件设备需要支持,可以添加相应的设备驱动程序。首先,查找内核中是否已经存在类似的驱动,如果没有,则需要根据设备的硬件规格编写新的驱动代码。
-
实现新的功能
:可以根据应用需求,在内核中实现新的功能,如文件系统扩展、网络协议支持等。这可能需要对内核的相关模块进行修改和扩展。
-
优化系统性能
:通过调整内核参数、优化硬件配置等方式,进一步提高系统的性能和稳定性。可以使用性能分析工具,如
oprofile
或
perf
,来找出系统的性能瓶颈并进行优化。
13. 总结
将 Linux 内核移植到新的开发板是一个复杂但有意义的过程。通过本文介绍的方法和步骤,我们可以成功地将 Linux 内核移植到基于飞思卡尔 MPC5200 的 PowerDNA Controller 开发板上。在移植过程中,我们需要深入了解内核的工作原理和硬件的特性,仔细处理每一个细节,以确保内核能够正确运行。同时,我们还可以通过优化和扩展,进一步提高系统的性能和功能,满足不同的应用需求。
希望本文的内容能够对大家在 Linux 内核移植方面有所帮助,让大家在开发嵌入式系统时能够更加得心应手。
graph LR
A[内核移植开始] --> B[引导加载程序初始化]
B --> C[找到基线内核配置]
C --> D[定制内核初始化]
D --> E[早期变量访问处理]
E --> F[开发板信息结构处理]
F --> G[设置 ppc_md 结构体]
G --> H[内核移植完成]
H --> I[常见问题解决]
I --> J[移植优化]
J --> K[后续扩展]
以上流程图总结了整个内核移植的过程,从开始到完成,再到后续的问题解决、优化和扩展,为大家提供了一个清晰的思路。通过遵循这些步骤,我们可以更加高效地完成 Linux 内核的移植工作。
超级会员免费看
2462

被折叠的 条评论
为什么被折叠?



