U-Boot在S3C2410上的移植

 

  U-Boot是用于初始化目标板硬件,为嵌入式操作系统提供目标板硬件配置信息,完成嵌入式操作系统装载、引导和运行的固件程序。它能够将系统的软硬件紧密衔接在一起。S3C2410是三星公司的一款基于ARM920T核的嵌入式通用处理器。本文将详细介绍U-Boot在S3C2410 开发板上的移植与运行。

  U-BOOT简介

  U-Boot支持ARM、 PowerPC等多种架构的处理器,也支持Linux、NetBSD和VxWorks等多种操作系统。它提供启动加载和下载两种工作模式。启动加载模式也称自主模式,一般是将存储在目标板Flash中的内核和文件系统的镜像装载到SDRAM中,整个过程无需用户的介入。在使用嵌入式产品时,一般工作在该模式下。工作在下载模式时,目标板往往受外设(一般是PC机)的控制,从而将外设中调试好的内核和文件系统下载到目标板中去。U-Boot允许用户在这两种工作模式间进行切换。通常目标板启动时,会延时等待一段时间,如果在设定的延时时间范围内,用户没有按键,U-Boot就进入启动加载模式。

  开发板的主要配置包括三星ARM9处理器S3C2410、1个串口和JTAG接口,晶振为12MHz,系统主频为200MHz。另外,开发板上还包括1片4M ×16位数据宽度的Flash,地址范围为0x01000000~0x01800000和2片8M×16位数据宽度的SDRAM,地址范围为 0x30000000~0x32000000。Flash使用了2410处理器的BANK0单元,由于2410中地址是循环映射的,因而 0x01000000 和0x0地址等同。

  在本系统中,U-Boot的主要功能包括:建立和初始化RAM;初始化一个串口;检测机器的体系结构,传递MACH_TYPE_xxx的值(SMDK2410)给内核;建立内核的标记列表(tagged list);调用内核镜像。

  U-Boot移植步骤

  为了使U-Boot支持新的开发板,一种简便的做法是在U-Boot已经支持的开发板中选择一种和目标板接近的,并在其基础上进行修改。代码修改的步骤如下:

  1)在board目录下创建smdk2410目录,添加smdk2410.c、flash.c、memsetup.s、u-boot.lds和config.mk等;
  2)在cpu目录下创建arm920t目录,主要包含start.s、interrupts.c、cpu.c、serial.c和speed.c等文件;
  3)在include/configs目录下添加smdk2410.h,它定义了全局的宏定义等;
  4)修改u-boot根目录下的Makefile文件:
  smdk2410_config : unconfig@./mkconfig $(@:_config=) arm arm920t smdk2410
  5)运行make smdk2410_config,如果没有错误,就可以开始进行与硬件相关的代码移植工作。由于这部分代码与硬件紧密相关,所以要熟悉开发板的硬件配置,可参考各芯片的用户手册。
       
  U-Boot启动过程

  U-Boot的启动过程可以分成3个阶段。首先在Flash中运行汇编程序,将Flash中的启动代码部分复制到SDRAM中,同时创造环境准备运行C程序;然后在SDRAM中执行,对硬件进行初始化;最后设置内核参数的标记列表,复制镜像文件,进入内核的入口函数。

  1) 程序首先在Flash中运行CPU入口函数/cpu/arm920t/start.s。具体工作包括:设置异常的入口地址和异常处理函数;配置 PLLCON寄存器,确定系统的主频;屏蔽看门狗和中断;初始化I/O寄存器;关闭MMU功能;调用/board/smdk2410中的 memsetup.s,初始化存储器空间,设置刷新频率;将U-Boot的内容复制到SDRAM中;设置堆栈的大小,ldr pc, _start_armboot。

  board/s3c2410中config.mk文件(TEXT_BASE = 0x31F00000)用于设置程序编译连接的起始地址,在程序中要特别注意与地址相关指令的使用。

  当程序在Flash中运行时,执行程序跳转时必须要使用跳转指令,而不能使用绝对地址的跳转(即直接对PC操作)。如果使用绝对地址,那么,程序的取指是相对于当前PC位置向前或者向后的32MB空间内,而不会跳入SDRAM中。

  2) 程序跳转到SDRAM中执行/lib_arm/board.c中的start_armboot()函数。该函数将完成如下工作:

  *设置通用端口rGPxCON;rGPxUP;设置处理器类型gd->bd->bi_arch_number = 193;设置启动参数地址gd->bd->bi_boot_params = 0x30000100;

  * env_init:设置环境变量,初始化环境;
  * init_baudrate:设置串口的波特率;
  * serial_init:设置串口的工作方式;
  * flash_init:设置ID号、每个分页的起始地址等信息,将信息送到相应的结构体中;
  * dram_init:设置SDRAM的起始地址和大小;
  * env_relocate:将环境变量的地址送到全局变量结构体中(gd->env_addr = (ulong)&(env_ptr->data));
  * enable_interrupts:开启中断;
  * main_loop:该函数主要用于设置延时等待,从而确定目标板是进入下载操作模式还是装载镜像文件启动内核。在设定的延时时间范围内,目标板将在串口等待输入命令,当目标板接到正确的命令后,系统进入下载模式。在延时时间到达后,如果没有接收到相关命令,系统将自动进入装载模式,执行bootm 30008000 30800000命令,程序进入do_bootm_linux()函数,调用内核启动函数;

  3) 装载模式下系统将执行do_bootm_linux()函数,0x30008000是内核在SDRAM中的起始地址;0x30800000是 ramdisk在SDRAM中的起始地址;0x40000是内核在Flash中的位置,0x100000是数据块的大小;0x140000是 ramdisk在FLASH中的位置,0x440000是数据块的大小。系统调用memcpy()函数将内核从flash和ramdisk复制到 SDRAM中,具体如下:

  memcpy((void *)0x30008000, (void *)0x40000, 0x100000);//复制数据块
  memcpy((void *)0x30800000, (void *)0x140000, 0x440000);//复制数据块

  通常,将内核参数传递给Linux操作系统有两种方法:采用struct param_struct结构体或标记列表。本系统中采用了第二种方法。

  一个合法的标记列表开始于ATAG_CORE,结束于ATAG_NONE。ATAG_CORE可以为空,一个空的ATAG_CORE的size字段设为

  “2”(0x00000002)。ATAG_NONE 的size字段必须设为“0”。标记列表可以有任意多的标记(tag)。在嵌入式Linux系统中,通常由U-Boot设置的启动参数有: ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。

  在本系统中,传递参数时分别调用了以下tag:

  setup_start_tag(bd);   //标记列表开始
  setup_memory_tags(bd); //设置内存的起始位置和大小
  setup_commandline_tag(bd, commandline); /*Linux内核在启动时可以命令行参数的形式来接收信息,利用这一点可以向内核提供那些内核不能检测的硬件参数信息,或者重载(override)内核检测到的信息,这里char *commandline "initrd=0x30800000,0x440000 root=/dev/ram init=/linuxrc console=ttyS0";*/
  setup_ramdisk_tag(bd);  //表示内核解压后ramdisk的大小
  setup_initrd_tag(bd, initrd_start, initrd_end); //设置ramdisk的大小和物理起始地址
  setup_end_tag(bd);    //标记列表结束

  其中bd_t *bd = gd->bd是指向bd_t 结构体的指针,在该结构体中存放了关于开发板配置的基本信息。标记列表应该放在内核解压和initrd的bootp程序都不会覆盖的内存区域,同时又不能和异常处理的入口地址相冲突。建议放在RAM起始的16K大小处,在本系统中即为0x30000100处。

  U-BOOT调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START+0x8000地址处。在跳转时,要满足下列条件:

  a) CPU寄存器的设置:R0=0;R1=机器类型 ID,本系统的机器类型ID=193。R2=启动参数标记列表在RAM中的起始基地址;
  b) CPU模式:必须禁止中断(IRQs和FIQs);CPU必须工作在SVC模式;
  c) Cache和MMU的设置:MMU 必须关闭;指令Cache可以打开也可以关闭;数据Cache必须关闭。

  系统采用下列代码来进入内核函数:

  theKernel = (void (*)(int, int))ntohl(hdr->ih_ep);
  theKernel(0, bd->bi_arch_number);其中,hdr是image_header_t类型的结构体,hdr->ih_ep指向内核的第一条指令地址,即Linux操作系统下的/kernel/arch/arm/boot/compressed/head.S汇编程序。theKernel() 函数调用应该不会返回,如果该调用返回,则说明出错。

  结语

  本文总结介绍了U-Boot在S3C2410上的移植,移植完成后,U-Boot能够稳定地运行在开发板上,为后续的软件开发打下较好的基础。

 

关于串口终端
在 boot loader 程序的设计与实现中,没有什么能够比从串口终端正确地收到打印信息能更令人激动了。此外,向串口终端打印信息也是一个非常重要而又有效的调试手段。但是,我们经常会碰到串口终端显示乱码或根本没有显示的问题。造成这个问题主要有两种原因:(1) boot loader 对串口的初始化设置不正确。(2) 运行在 host 端的终端仿真程序对串口的设置不正确,这包括:波特率、奇偶校验、数据位和停止位等方面的设置。

此外,有时也会碰到这样的问题,那就是:在 boot loader 的运行过程中我们可以正确地向串口终端输出信息,但当 boot loader 启动内核后却无法看到内核的启动输出信息。对这一问题的原因可以从以下几个方面来考虑:

(1) 首先请确认你的内核在编译时配置了对串口终端的支持,并配置了正确的串口驱动程序。

(2) 你的 boot loader 对串口的初始化设置可能会和内核对串口的初始化设置不一致。此外,对于诸如 s3c44b0x 这样的 CPU,CPU 时钟频率的设置也会影响串口,因此如果 boot loader 和内核对其 CPU 时钟频率的设置不一致,也会使串口终端无法正确显示信息。 

(3) 最后,还要确认 boot loader 所用的内核基地址必须和内核映像在编译时所用的运行基地址一致,尤其是对于 uClinux 而言。假设你的内核映像在编译时用的基地址是 0xc0008000,但你的 boot loader 却将它加载到 0xc0010000 处去执行,那么内核映像当然不能正确地执行了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值