390 likes | 598 Views
嵌入式 Linux 移植. 广东省嵌入式软件公共技术中心 2007 年 7 月 14 日. 课程介绍. 主要介绍: linux 内核移植到开发板的过程 内核映象文件的启动流程. 内核移植介绍. 内核移植介绍 Linux 内核移植就是从一种硬件平台转移到另一种硬件平台上运行。 由于硬件板的变化,内核移植是嵌入式 linux 系统中最常见的一项工作。 内核移植主要是修改跟硬件平台相关的代码,添加驱动,一般不涉及 linux 内核通用的程序。. 内核移植前的测试. Linux 内核在不断地更新,支持了各种体系结构的多种目标板;
E N D
嵌入式Linux移植 广东省嵌入式软件公共技术中心 2007年7月14日
课程介绍 • 主要介绍: • linux内核移植到开发板的过程 • 内核映象文件的启动流程
内核移植介绍 • 内核移植介绍 • Linux内核移植就是从一种硬件平台转移到另一种硬件平台上运行。 • 由于硬件板的变化,内核移植是嵌入式linux系统中最常见的一项工作。 • 内核移植主要是修改跟硬件平台相关的代码,添加驱动,一般不涉及linux内核通用的程序。
内核移植前的测试 • Linux内核在不断地更新,支持了各种体系结构的多种目标板; • 我们很容易从中找到跟自己硬件平台类似的目标板; • 移植的第一步工作就是参考内核已经支持的目标板来移植,就如同使用模板开发程序。
移植的工作 • 选择参考板 • 原则: • 参考板与开发板具有相同的处理器; • 参考板与开发板具有相同的外围接口电路; • Linux内核已经支持了参考板,至少有参考板的补丁; • 参考板linux设备驱动正常工作; • 因为半导体公司在发布一块新的处理器的时候,一般都会提供参考设计板和Linux BSP包,世界各地的linux爱好者也在不断地更新linux内核。通常我对都可以找到跟自己硬件平台类似的参考板。
移植的工作 • 编译测试参考板的linux内核 • 为了确信linux对参考板的支持情况,最好是验证,配置编译linux内核,在目标板上运行测试一下。 • 例如参考板是smdk2410,步骤如下: • 在顶层Makefile中设置ARCH、CROSS_COMPILE变量; ARCH:=arm CROSS_COMPILE:=arm-linux- • 使用参考板的缺省内核配置, make smdk2410_defconfig • 保存配置以后,执行make编译内核 • 编译完成后,得到内核映像文件arch/arm/boot/zImage • 下载内核映像到目标板中,测试内核的运行情况
移植的工作 • 分析参考板的BSP代码 • 如果linux内核已经支持了参考板,下一步工作就是要熟悉参考板的BSP代码: • 分析平台相关的部分代码实现; • 分析内核编译组织方式; • 分析内核启动流程; • 分析驱动程序的实现。 • 熟悉了参考板的这些代码之后,就可以动手修改内核源码了。
开发板内核移植 • 内核移植的主要工作,就是添加开发板初始化和驱动程序的代码; • 这部分代码都是跟体系结构相关的,在arch目录下按照不同的体系结构管理。 • 下面以ARM S3C2410平台为例,分析内核移植过程。
开发板的配置选项 • 开发板的配置选项 • ARM平台相关的选项在arch/arm目录下实现; • 在顶层Makefile中设置相应的体系结构和工具链,这样在配置linux内核的时候就会调用arch/arm/Kconfig文件; • Kconfig文件是内核主配置文件,在文件中可以找到“System Type”选项:
开发板的配置选项 • #arch/arm/Kconfig menu “System Tpye” choise prompt “ARM system type” default ARCH_RPC …… config ARCH_S3C2410 bool “Samsung S3C2410” …… source “arch/arm/mach-s3c2410/Kconfig” ……
开发板的配置选项 • 上面的“choice”语句可以在菜单中生成一个多选项 • 这里找到“Samsung S3C2410”选项; • 然后通过source语句调用arch/arm/mach-s3c2410/Kconfig文件; • Arch/arm/mach-s3c2410/Kconfig文件定义了各种S3C2410处理器开发板的选项,还有 S3C2410处理器的特殊支持选项; • 通过上面两个Kconfig文件,提供了处理器和开发板以及处理器特征的选项。
开发板在内核中的描述 • 开发板在内核中的描述 • “arch/arm/mach-s3c2410”目录是专门保存S3C2410系列处理器平台相关程序; • 目录下的文件可分为三种: • Kconfig和Makefile,用于内核配置编译 • 处理器通用的程序,如:clock.c、clock.h、cpu.c、cpu.h、s3c2410.c、s3c2410.h等 • 目标板相关的,如:bast.h、bast-irq.c、mach-bast.c等 • 在这些文件中,实现了处理器和目标板相关的一些定义和初始化函数。 • 另外,还有一下头文件的在include/asm-arm/arch-s3c2410/下。
开发板在内核中的描述 • 在arch/arm/mach-s3c2410/mach-smdk2410.c中定义了SMDK2410开发板在内核中的描述; #arch/arm/mach-s3c2410/mach-smdk2410.c MACHINE_START(SMDK2410, "SMDK2410") .phys_ram = S3C2410_SDRAM_PA, .phys_io = S3C2410_PA_UART, .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, .boot_params = S3C2410_SDRAM_PA + 0x100, .map_io = smdk2410_map_io, .init_irq = smdk2410_init_irq, .timer = &s3c24xx_timer, MACHINE_END
开发板在内核中的描述 • 在include/asm-arm/mach/arch.h文件中定义了MACHINE_START和MACHINE_END宏; • 这个宏其实是用machine_desc结构体来描述目标板硬件平台。这个结构体的成员包括: • 系统平台号(nr,architecture number); • 内存起始地址(phys_ram); • I/O起始地址(phys_io); • 系统平台名称(name); • 启动参数(boot_params); • 初始化函数指针等。
开发板在内核中的描述 • 上面的定义相当于下面的结构体 const struct machine_desc __mach_desc_SMDK2410 \ __attribute__((__section__(".arch.info"))) = { \ .nr = MACH_TYPE_SMDK2410, \ .name = “SMDK2410”, …… }; • 其中MACH_TYPE_SMDK2410是SMDK2410的系统平台号,在arch/arm/tools/mach-tpyes中定义;
开发板在内核中的描述 • 在mach_desc_SMDK2410结构体中,还有一些系统初始化函数: • smdk2410_map_io(); • smdk2410_init_irq(); • s3c24xx_timer() 等; • 这些函数在其他文件中实现,并且在内核启动过程中,将通过该结构体调用这些函数,完成系统平台初始化工作。
添加驱动程序 • Linux-2.6内核已经支持s3c2410处理器,跟硬件平台相关的程序基本上已经完整了; • 而s3c2410属于片上系统,处理器芯片具备串口、显示等外围接口的控制器,这样,参考板的设备驱动多数可以直接使用; • 但是,不同的开发板可以使用不同的flash、以太网接口芯片、LCD屏等; • 这就需要根据硬件修改或者开发驱动程序。
添加驱动程序 • GEC2410开发板的网络接口使用CS8900A芯片,但是linux-2.6.14的内核还没有支持,因此需要自己添加这块芯片的驱动。 • 在网上可以找到针对CS8900A芯片的驱动程序; • 下面主要介绍怎么把写好的网络驱动添加到内核源码中 ; • CS8900A的驱动源文件:cirrus.h 、cirrus.c
实例:添加网络驱动 • 步骤如下: 1、复制源文件到内核源码的drivers/net/目录 cp cirrus.h drivers/net/ cp cirrus.c drivers/net/
实例:添加网络驱动 2、添加配置选项 主要是修改drivers/net/Kconfig文件 cd drivers/net vi Kconfig 添加如下: config CIRRUS tristate "CS8900 Ethernet support by GEC“ depends on ARM ---help--- Support for cs8900
实例:添加网络驱动 3、修改drivers/net/makefile,添加编译目标 vi Makefile 添加内容如下: obj-$(CONFIG_CIRRUS) += cirrus.o
实例:添加网络驱动 4、驱动程序对外围设备的访问分为内存映射和I/O两种,对于CS8900A,我们采用内存映射,因此,需要加入cs8900A的物理地址到虚拟地址的映射; vi arch/arm/mach-s3c2410/mach-smdk2410.c 添加内容如下: static struct map_desc smdk2410_iodesc[] __initdata = { /* nothing here yet */ {S3C2410_VA_CS8900,S3C2410_PA_CS8900,SZ_1M,MT_DEVICE} }
实例:添加网络驱动 5、上面用到的S3C24X0_VA_CS8900和S3C24X0_PA_CS8900,可以在include/asm-arm/arch-s3c2410/map.h文件中定义: vi linux/include/asm-arm/arch-s3c2410/map.h 添加如下: /* CS8900 */ #define S3C2410_VA_CS8900 (0xE0000000) #define S3C2410_PA_CS8900 (0x19000000)
实例:添加网络驱动 • 这样,CS8900的驱动就添加完毕了,在配置编译内核的时候,可以在选择菜单上选上“CS8900 Ethernet support by GEC”,这样就可以把CS890A的启动编译进内核了。
移植后的工作 • 内核移植完成之后,就可以发布内核补丁,基于一个稳定的内核版本制造补丁文件,可以方便的保存和发布。 • 制作补丁的步骤如下: • 没有修改过的内核源码目录linux-2.6.14.1,修改过的内核源码目录linux-gec2410 cd linux-gec2410 cp .config arch/arm/configs/smdk2410_defconfig make mrproper cd .. diff –rNu ./linux-2.6.14.1 linux-gec2410>patch-linux-2.6.14-2410 • 这样就得到补丁文件patch-linux-2.6.14-2410。
内核移植实验 • 移植编译基于2410的内核 • 添加TS、LCD驱动
内核的引导 • bootloader引导内核的过程 • 将linux内核映象拷贝到RAM中; • 设置传递给内核的启动参数; • 跳转到RAM中的内核映象地址,将控制权交给内核; • 内核映象自引导过程 • 典型的内核映象是zImage,它是经过压缩的、具备自引导能力的linux内核映象; • zImage映象的引导过程就是解压和启动vmlinux的过程。
内核启动参数的设置 • bootloader将内核映像拷贝到 RAM 空间中后,就可以准备启动 Linux 内核了。但是在调用内核之前,应该作一步准备工作,即:设置 Linux 内核的启动参数。 • 将参数从引导装载程序传递到内核的方法之一是参数结构体方法(parameter_structure) 。 • 这种方法就是在内存开始地址处,设置一个参数结构体; • 参数结构体的起始地址一般是(内存起始地址+0x100)处,如2410的参数结构体的起始地址是(0x30000000+0x100)。
内核启动参数的设置 • 参数结构体(struct param_struct); struct param_struct { union { struct { unsigned long page_size; unsigned long nr_pages; …… } s; …… } u1; …… char commandline[COMMAND_LINE_SIZE]; };
内核启动参数的设置 • 关于参数结构体的成员在内核源码中的Documentation\arm\setup文件中有详细介绍; • 这里只介绍三个必要的成员: • page_size :页面大小 • nr_pages :内存中的页面数 • commandline :命令行参数
内核启动参数的设置 • Boot Loader 在启动内核之前,除了要设置启动参数,还要求满足以下条件 : 1. CPU 寄存器的设置:R0=0;R1=机器类型 ID;R2=启动参数在 RAM 中起始基地址; 2. CPU 模式: 必须禁止中断(IRQs和FIQs);CPU 必须 SVC 模式; 3. Cache 和 MMU 的设置:MMU 必须关闭; 指令 Cache 可以打开也可以关闭; 数据 Cache 必须关闭;
u-boot的go命令 • u-boot启动内核映象的其中一个命令是“go ”命令; • “go”命令的实现代码在u-boot源码的common/cmd_boot.c文件中: int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { …… printf ("## Starting application at 0x%08lX ...\n", addr); setup_linux_param(0x30000100);//设置启动参数 call_linux(0,0x0c1,addr); //启动内核 …… }
u-boot的go命令 • setup_linux_param函数的实现 static void setup_linux_param(ulong param_base) { //定义参数结构体,和命令行参数变量 struct param_struct *params = (struct param_struct *)param_base; char *linux_cmd; linux_cmd = getenv(“bootargs”); //获取命令行参数 memset(params, 0, sizeof(struct param_struct)); //初始化为0 params->u1.s.page_size = 0x00001000; //设置页面大小 params->u1.s.nr_pages = (0x04000000 >> 12); //设置内存的页面数 memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1); //设置命令行参数 }
u-boot的go命令 • call_linux函数的实现 void call_linux(long a0, long a1, long a2) { /*第一段asm使cache失效*/ __asm__(......); /*第二段asm使地址变换后备缓冲器失效*/ __asm__(......); __asm__( “mov r0, %0\n“ //r0 = a0 = 0 “mov r1, #0x0c1\n“ //r1=2410的机器类型 ID,值为193 /“mov r2, %2\n“ //r2 = a2 =内核映象在内存的地址 ...... “mov pc, r2\n //跳转到内核映象在内存中的地址 ); }
内核自引导过程 • vmlinux映像介绍 • vmlinux映像是内核在虚拟空间运行时,代码的真实反映; • Vmlinux运行在虚拟地址,不具备引导能力; • zImage映像介绍 • zImage是可引导的 ,压缩的内核映像,也是vmlinuz, 它是 vmlinux 的压缩映像。 • zImage跟体系结构很有关系,不同体系结构的内核一般有不同的格式; • zImage 包含2部分:压缩的 vmlinux和自引导程序;
zImage的自引导程序 • zImage映像的入代码是自引导程序;也就是说,当CPU跳转到zImage的时候,第一个执行的是自引导程序; • 自引导程序的主要责任是解压zImage中的vmlinux,并且引导vmlinux; • 自引导程序的入口在 arch/$(ARCH)/boot/head.S文件中;
vmlinux启动过程 • CPU跳转到vmlinux的入口地址时,顺序执行内核启动程序,其中包括; • 初始化内核各个子系统; • 挂载根文件系统; • 初始化驱动程序; • 启动用户空间的init进程等。
启动用户空间的init进程 • Linux内核在挂载根文件系统之后,要执行文件系统中的应用程序; • 内核首先检查在命令行参数中有没有指定内核执行的第一个应用程序; • 如果没有指定,则顺序执行/sbin/init, /etc/init, /bin/init, /bin/sh; • 如果都没有这些程序,就会打印panic信息。
小结 • 内核启动的最后进入用户空间,将控制权交给用户; • 内核启动的第一个用户程序是init程序; • 下节课介绍linux在用户空间的启动流程;