Vivi(Bootloader)源代码分析与移植 - 图文

更新时间:2024-04-28 02:42:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Bootloader(Vivi)源代码分析 ----基于S3C2410处理器

1

目 录

第一章 绪论 ........................................................................................................................................................ 3

1.1 Vivi简介 ................................................................................................................................................ 3 1.2 Vivi 源码树 ........................................................................................................................................... 4 1.3 Vivi 支持的主要功能 ........................................................................................................................... 5 第二章 Vivi源代码详细分析 ............................................................................................................................ 6

2.1 Vivi的启动流程 .................................................................................................................................... 6

2.1.1 第一阶段(Stage 1) ................................................................................................................ 6 2.1.2 第二阶段(Stage 2) ...............................................................................................................11 2.2 Vivi 的初始化 .................................................................................................................................. 14

2.2.1 设置异常向量 .......................................................................................................................... 14 2.2.2 初始化 ...................................................................................................................................... 15 2.2.3中断向量表 ............................................................................................................................... 24 2.2.4 关于vivi magic number ........................................................................................................... 27 2.2.5 实际完成的主要任务: ........................................................................................................ 28 2.3 stage 2:【init/main.c】................................................................................................................... 29

2.3.1 step1打印版本信息 ............................................................................................................... 29 2.3.2 step2初始化GPIO ................................................................................................................ 31 2.3.3 step3MMU初始化 ................................................................................................................. 32 2.3.4 step4 堆初始化...................................................................................................................... 32 2.3.5 step5 MTD设备初始化 ...................................................................................................... 35 2.3.6 step 6私有参数都放在预先规划的内存区域 ...................................................................... 43 2.3.7 step7调用add_command()函数,增加vivi作为终端时命令的相应处理函数 ................ 48 2.3.8 step8要么进入vivi的命令行交互界面,要么直接启动内核 ........................................... 50

第三章 U-boot 在S3C2410 上的移植分析 ................................................................................................... 52

3. 1 对ARM-920T内核的支持 ........................................................................................................... 52 3. 2 配置自己的开发板 ........................................................................................................................ 52 3. 3 实现网卡的驱动程序 .................................................................................................................... 54 3.4 从NAND Flash启动 .......................................................................................................................... 55

3.4.1 修改cpu/arm920t/start.s 添加NAND Flash启动跳转代码 ................................................. 55 3.4.2 添加从NAND Flash启动代码 ............................................................................................... 57 3.4.3 添加上述代码中引用的宏定义 .............................................................................................. 59 3. 5 修改Makefile 文件....................................................................................................................... 59 3.6 搭建编译环境 ..................................................................................................................................... 60 3. 7 生成目标文件并进行测试 ............................................................................................................ 61 3. 8 测试 ................................................................................................................................................ 63

2

第一章 绪论

1.1 Vivi简介

Vivi 来自韩国是由mizi 公司开发的bootloader, 适用于ARM9处理器,它是三星官方板SMDK2410采用的Bootloader,通过修改后可以支持S3C2440等处理器。 Vivi有两种工作模式:启动加载模式和下载模式。启动加载模式可以在一段时间后(这个时间可更改)自行启动linux内核,这时vivi的默认模式。在下载模式下,vivi为用户提供一个命令行接口,通过接口可以使用vivi提供的一些命令,见下表:

命令 功能 Load 把二进制文件载入Flash或RAM Part 操作MTD分区信息。显示、增加、删除、复位、保存MTD分区 Param 设置参数 Boot 启动系统 Flash 管理Flash,如删除Flash的数据 Vivi的主要特点就是代码小巧 ,有利于移植新的处理器。同时vivi的软件结构和配置方法类似linux

风格,对于有编译过linux内核经验的读者,vivi更容易上手

Vivi支持网卡,USB接口,LCD驱动,MTD,支持yaffs文件系统固化等功能。

3

1.2 Vivi 源码树

包括arch/drivers/include/init/lib/scripts/util等几个目录

|-arch ------------------- 此目录主要包含所有vivi支持的目标板的子目录 | |- s3c2410 ------------- s3c2410的支持代码 | |- def-configs --------- 硬件平台配置文件

|-drivers ----------------- 包括了引导内核需要的设备的驱动程序(MTD和串口) | |- mtd ----------------- MTD设备驱动程序 | |- serial -------------- 串口设备驱动程序

|-init -------------------- main.c文件和version.c文件 |-include ----------------- 头文件目录 |-lib --------------------- 公共接口代码 |-util -------------------- 工具的代码 |-scripts ----------------- 脚本文件

vivi的代码包括arch,init,lib,drivers和include等几个目录,共200多条文件。 Vivi主要包括下面几个目录: arch:此目录包括了所有vivi支持的目标板的子目录,例如s3c2410目录。 drivers:其中包括了引导内核需要的设备的驱动程序(MTD和串口)。MTD目录下分map、nand和nor三个目录。 init:这个目录只有main.c和version.c两个文件。和普通的C程序一样,vivi将从main函数开始执行。 lib:一些平台公共的接口代码,比如time.c里的udelay()和mdelay()。 include:头文件的公共目录,其中的s3c2410.h定义了这块处理器的一些寄存器。Platform/smdk2410.h定义了与开发板相关的资源配置参数,我们往往只需要修改这个文件就可以配置目标板的参数,如波特率、引导参数、物理内存映射等。

4

1.3 Vivi 支持的主要功能

Vivi目前只支持使用串口和主机通信,所以必须使用一条串口电缆来连接目标板和主机。 他有如下作用:

? ? ? ?

把内核(kemel)从flash复制到RAM,然后启动它 初始化硬件

下载程序并写入flash(一般通过串口或者网口先把内核下载到RAM中,然后写入到Flash) 检测目标板(bootloader会有一些简单的代码用以测试目标板硬件的好坏)

5

第二章 Vivi源代码详细分析

2.1 Vivi的启动流程

由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。

2.1.1 第一阶段(Stage 1)

第一阶段的启动代码在cpu\\\\head.s 中,完成的工作主要有: ? 硬件设备初始化。

? 为加载 Boot Loader 的 stage2 准备 RAM 空间。 ? 拷贝 Boot Loader 的 stage2 到 RAM 空间中。 ? 设置好堆栈。

? 跳转到 stage2 的 C 入口点。

1 、基本的硬件初始化

这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序): 1. 屏蔽所有的中断。为中断提供服务通常是 OS 设备驱动程序的责任,因此在 Boot

Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器(比如 ARM 的 CPSR 寄存器)来完成。 Reset: @ disable watch dog timer ;禁止看门狗计时器

mov r1, #0x53000000 ;WTCON寄存器地址是

0x53000000,清0

mov r2, #0x0 str r2, [r1]

#ifdef CONFIG_S3C2410_MPORT3 ;不符合条件,跳到下面的关中断 /**** 在/vivi/include/autoconf.h中#undef CONFIG_S3C2410_MPORT3******/ mov r1, #0x56000000 ;GPACON寄存器地址是

0x56000000

mov r2, #0x00000005 str r2, [r1, #0x70] ;配置GPHCON寄存器 mov r2, #0x00000001 str r2, [r1, #0x78] ;配置GPHUP寄存器 mov r2, #0x00000001

6

str r2, [r1, #0x74] #endif @ disable all interrupts mov r1, #INT_CTL_BASE mov r2, #0xffffffff str r2, [r1, #oINTMSK] ldr r2, =0x7ff str r2, [r1, #oINTSUBMSK]

;配置GPHDAT寄存器 ;禁止全部中断

;掩码关闭所有中断

2. 设置 CPU 的速度和时钟频率。 @ initialise system clocks mov r1, #CLK_CTL_BASE mvn r2, #0xff000000 str r2, [r1, #oLOCKTIME] @ldr r2, mpll_50mhz @str r2, [r1, #oMPLLCON]

;初始化系统时钟

3. RAM 初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存

器等。 ENTRY(memsetup) @ initialise the static memory @ set memory control registers ;设置内存控制寄存器的初值 mov r1, #MEM_CTL_BASE adrl r2, mem_cfg_val

4. 初始化 LED。典型地,通过 GPIO 来驱动 LED,其目的是表明系统的状态是 OK 还

是 Error。如果板子上没有 LED,那么也可以通过初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息来完成这一点。 @ All LED on ;点亮开发板上的LED mov r1, #GPIO_CTL_BASE add r1, r1, #oGPIO_F ;LED使用GPIOF组的管脚

ldr r2,=0x55aa ;使能EINT0,EINT1,EINT2,EINT3,

;另四个管脚配置成输出,屏蔽EINT4,5,6,7

str r2, [r1, #oGPIO_CON] mov r2, #0xff str r2, [r1, #oGPIO_UP] ;disable the pull-up function mov r2, #0x00 str r2, [r1, #oGPIO_DAT] #endif

7

5. 关闭 CPU 内部指令/数据 cache。

2、 为加载 stage2 准备 RAM 空间

为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。

由于 stage2 通常是 C 语言执行代码,因此在考虑空间大小时,除了 stage2 可执行映象的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小(通常是 4KB)的倍数。一般而言,1M 的 RAM 空间已经足够了。具体的地址范围可以任意安排,比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是,将 stage2 安排到整个 RAM 空间的最顶 1MB(也即(RamEnd-1MB) - RamEnd)是一种值得推荐的方法。

为了后面的叙述方便,这里把所安排的 RAM 空间范围的大小记为:stage2_size(字节),把起始地址和终止地址分别记为:stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此:

stage2_end=stage2_start+stage2_size

另外,还必须确保所安排的地址范围的的确确是可读写的 RAM 空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于 blob 的方法,也即:以 memory page 为被测试单位,测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为:test_mempage,其具体步骤如下: 1. 先保存 memory page 一开始两个字的内容。

2. 向这两个字中写入任意的数字。比如:向第一个字写入 0x55,第 2 个字写入 0xaa。 3. 然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是 0x55 和 0xaa。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。 4. 再向这两个字中写入任意的数字。比如:向第一个字写入 0xaa,第 2 个字中写入 0x55。

5. 然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是 0xaa 和 0x55。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。

6. 恢复这两个字的原始内容。测试完毕。 为了得到一段干净的 RAM 空间范围,我们也可以将所安排的 RAM 空间范围进行清零操作。

@ set GPIO for UART mov r1, #GPIO_CTL_BASE add r1, r1, #oGPIO_H ldr r2, gpio_con_uart str r2, [r1, #oGPIO_CON] ldr r2, gpio_up_uart str r2, [r1, #oGPIO_UP]

;设置串口

;设置GPIO_H组管脚为串口

8

bl InitUART

;跳转到InitUART串口初始化函数

1.1.3 拷贝 stage2 到 RAM 中 拷贝时要确定两点:

(1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址; (2) RAM 空间的起始地址。 1:

1.1.4 设置堆栈指针 sp

堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4),也即在 1.1.2 节所安排的那个 1MB 的 RAM 空间的最顶端(堆栈向下生长)。

此外,在设置堆栈指针 sp 之前,也可以关闭 led 灯,以提示用户我们准备跳转到 stage2。 经过上述这些执行步骤后,系统的物理内存布局应该如下图2所示。 @ get read to call C functions ldr sp, DW_STACK_START @ setup stack pointer mov fp, #0 @ no previous frame, so fp=0

1.1.5 跳转到 stage2 的 C 入口点

在上述一切都就绪后,就可以跳转到 Boot Loader 的 stage2 去执行了。比如,在 ARM 系统中,这可以通过修改 PC 寄存器为合适的地址来实现。

head.S 负责完成硬件初始化操作,具体分析见源码注释 ,汇编差不多忘光了,下面注释中有关汇编的东西多些。 @ get read to call C functions ldr sp, DW_STACK_START @ setup stack pointer mov fp, #0 @ no previous frame, so fp=0 mov a2, #0 @ set argv to NULL bl main @ call main

9

@ jump to ram

ldr r1, =on_the_ram add pc, r1, #0 nop nop b 1b @ infinite loop

mov pc, #FLASH_BASE @ otherwise, reboot

10

2.1.2 第二阶段(Stage 2)

vivi的第二阶段是从main()函数开始,同一般的C语言程序一样,该函数在/init/main.c文件中,总共可以分为8个步骤。

(1) 函数开始,通过putstr(vivi_banner)打印出vivi的版本。Vivi_banner在/init/version.c

文件中定义

(2) 对开发板进行初始化(board_init函数),board_init是与开发板紧密相关的,这个函

数在/arch/s3c2410/smdk.c文件中。开发板初始化主要完成两个功能,时钟初始化(init_time())和通用IO口设置(set_gpios())。

void set_gpios(void) {

GPACON = vGPACON; GPBCON = vGPBCON; GPBUP = vGPBUP; GPCCON = vGPCCON; GPCUP = vGPCUP; GPDCON = vGPDCON; GPDUP = vGPDUP; GPECON = vGPECON; GPEUP = vGPEUP; GPFCON = vGPFCON; GPFUP = vGPFUP; GPGCON = vGPGCON; GPGUP = vGPGUP; GPHCON = vGPHCON; GPHUP = vGPHUP; EXTINT0 = vEXTINT0; EXTINT1 = vEXTINT1; EXTINT2 = vEXTINT2;

}

其中,GPIO口在smdk2410.h(\\vivi\\include\\platform\\目录下)文件中定义。 (3) 内存映射初始化和内存管理单元的初始化工作:

mem_map_init(); mmu_init();

这两个函数都在/arch/s3c2410/mmu.c文件中。 void mem_map_init(void) {

#ifdef CONFIG_S3C2410_NAND_BOOT mem_map_nand_boot(); #else

mem_map_nor(); #endif

cache_clean_invalidate();

11

tlb_invalidate(); }

如果配置vivi时使用了NAND作为启动设备,则执行mem_map_nand_boot(),否则执行mem_map_nor()。这里要注意的是,如果使用NOR启动,则必须先把vivi代码复制到RAM中。这个过程是由copy_vivi_to_ram()函数来完成的。代码如下: static void copy_vivi_to_ram(void)

{

putstr_hex(\

memcpy((void *)VIVI_RAM_BASE, (void *)VIVI_ROM_BASE, VIVI_RAM_SIZE); }

VIVI_RAM_BASE、VIVI_ROM_BASE、VIVI_RAM_SIZE这些值都可以在

smdk2410.h中查到,并且这些值必须根据自己开发板的RAM实际大小修改。这也是在移植vivi的过程中需要注意的一个地方。

mmu_init()函数中执行了arm920_setup函数。这段代码是用汇编语言实现的,针对arm920t核的处理器。

(4) 初始化堆栈,heap_init()。(定义在\\vivi\\lib\\heap.c文件中)

int heap_init(void) {

return mmalloc_init((unsigned char *)(HEAP_BASE), HEAP_SIZE);

}

(5) 初始化mtd设备,mtd_dev_init()。

int mtd_init(void) { int ret;

#ifdef CONFIG_MTD_CFI

ret = cfi_init();

#endif

#ifdef CONFIG_MTD_SMC

ret = smc_init();

#endif

#ifdef CONFIG_S3C2410_AMD_BOOT

ret = amd_init();

#endif

if (ret) { mymtd = NULL; return ret; } return 0;

}

这几个函数可以在/drivers/mtd/maps/s3c2410_flash.c里找到。 (6) 初始化私有数据,init_priv_data()。(定义在\\vivi\\lib\\priv_data\\rw.c文件中) (7) 初始化内置命令,init_builtin_cmds()。

12

通过add_command函数,加载vivi内置的几个命令。 (8) 启动boot_or_vivi()。

启动成功后,将通过vivi_shell()启动一个shell(如果配置了CONFIG_SERIAL_TERM),此时vivi的任务完成。

13

2.2 Vivi 的初始化

2.2.1 设置异常向量

@ Exception vector table (physical address = 0x00000000) ;异常向量表物理地址 @

@ 0x00: Reset b Reset

@ 0x04: Undefined instruction exception UndefEntryPoint: b HandleUndef

@ 0x08: Software interrupt exception SWIEntryPoint: b HandleSWI

@ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort) PrefetchAbortEnteryPoint: b HandlePrefetchAbort

@ 0x10: Data Access Memory Abort DataAbortEntryPoint: b HandleDataAbort

@ 0x14: Not used NotUsedEntryPoint: b HandleNotUsed

@ 0x18: IRQ(Interrupt Request) exception IRQEntryPoint: b HandleIRQ

@ 0x1c: FIQ(Fast Interrupt Request) exception FIQEntryPoint: b HandleFIQ

下面是固定位置存放环境变量 @

@ VIVI magics

14

;复位;未定义的指令异常;软件中断异常;内存操作异常;数据异常;未使用;慢速中断处理;快速中断处理@

@ 0x20: magic number so we can verify that we only put .long 0 @ 0x24:

.long 0

@ 0x28: where this vivi was linked, so we can put it in memory in the right place

.long _start //_start用来指定链接后的起始装载地址装载到内存中的地址 @ 0x2C: this contains the platform, cpu and machine id .long ((1 << 24) | (6 << 16) | 193) @ 0x30: vivi capabilities .long 0

@ 0x34:

b SleepRamProc @

@ Start VIVI head @

2.2.2 初始化

Reset: //上电后cpu会从0x0地址读取指令执行,也就是b Reset

@ disable watch dog timer mov r1, #0x53000000 mov r2, #0x0 str r2, [r1]

# 121 \ @ disable all interrupts mov r1, #0x4A000000 mov r2, #0xffffffff

str r2, [r1, #0x08] //0x4A000008为中断屏蔽寄存器,将里面赋全1,表示屏蔽这32个中断源

ldr r2, =0x7ff

str r2, [r1, #0x1C] //0x4A00001C为中断子屏蔽寄存器,里面低11位也用来表示屏蔽掉这11个中断源

@ initialise system clocks mov r1, #0x4C000000

mvn r2, #0xff000000 //MVN指令可完成从另一个寄存器、被移位的寄存器、或将一个立即数加载到目的寄存器。与MOV指令不同之处是在传送之前按位被取反了,即把一个被取反的值传送到目的寄存器中。也就是r2=0x00ffffff

str r2, [r1, #0x00] //我们可以在程序开头启动MPLL,在设置MPLL的几个寄存器

15

后,需要等待一段 时间(Lock Time), MPLL的输出才稳定。在这段时间(Lock Time)内,FCLK停振,CPU停止工作。Lock Time的长短由寄存器LOCKTIME设定,Lock Time之后,MPLL输出正常,CPU工作在新的FCLK下,前面说过,MPLL启动后需要等待一段时间(Lock Time),使得其输出稳定。

位[23:12]用于UPLL,位[11:0]用于MPLL。本实验使用确省值0x00ffffff。

@ldr r2, mpll_50mhz @str r2, [r1, #0x04]

@ 1:2:4

mov r1, #0x4C000000 mov r2, #0x3

str r2, [r1, #0x14] //0x4C000014为分频寄存器,用于设置FCLK、HCLK、PCLK三者的比例

bit[2] — — HDIVN1,若为1,则 bit[1:0] 必 须 设 为 0b00 , 此 时FCLK:HCLK:PCLK=1:1/4:1/4;若为0,三者比例由bit[1:0]确定 bit[1]——HDIVN,0:HCLK=FCLK;1:HCLK=FCLK/2 bit[0]——PDIVN,0:PCLK=HCLK;1:PCLK=HCLK/2 本实验设为0x03,则FCLK:HCLK:PCLK=1:1/2:1/4

mrc p15, 0, r1, c1, c0, 0 @ read ctrl register orr r1, r1, #0xc0000000 @ Asynchronous mcr p15, 0, r1, c1, c0, 0 @ write ctrl register

上面这三句代码的意思是切换模式:If HDIVN = 1, the CPU bus mode has to be changed from the fast bus

mode to the asynchronous bus mode using following instructions:

MMU_SetAsyncBusMode mrc p15, 0, r0, c1, c0, 0

orr r0, r0, #R1_nF:OR:R1_iA mcr p15,0, r0, c1, c0, 0 其中的“R1_nF:OR:R1_iA”等于0xc0000000。意思就是说,当HDIVN = 1时,CPU bus mode需要从原来的“fast bus mode”改为“asynchronous bus mode”。

@ now, CPU clock is 200 Mhz mov r1, #0x4C000000 ldr r2, mpll_200mhz

str r2, [r1, #0x04] //0x4C000004为MPLLCON寄存器, 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV。有如下计算公式:

MPLL(FCLK) = (m * Fin)/(p * 2^s) 其中: m = MDIV + 8, p = PDIV + 2 对于本开发板,Fin = 12MHz,MPLLCON设为0x5c0040,可以计算出FCLK=200MHz,再由CLKDIVN的设置可知:HCLK=100MHz,PCLK=50MHz。 # 164 \

16

bl memsetup

@ Check if this is a wake-up from sleep

ldr r1, PMST_ADDR //0x560000B4为GSTATUS2寄存器,里面[0:2]三位有效 ldr r0, [r1] //将该寄存器中的值取出来保存到r0中

tst r0, #((1 << 1)) //测试r0的第一位。这位是Power_OFF reset. The reset after the wakeup from Power_OFF mode.The setting is cleared by writing \指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值。 bne WakeupStart

@ All LED on 这里就需要对GPIO口进行控制 mov r1, #0x56000000

add r1, r1, #0x50 //0x56000050是GPFCON用来配置port F端口 ldr r2,=0x55aa

str r2, [r1, #0x0] //设置为0101010110101010因为每两位用来控制一个引脚,也就是将GPF4-GPF7设置为输出,将GPF0-GPF3设置为中断 mov r2, #0xff

str r2, [r1, #0x8] //0x56000058为GPFUP为port F的上拉寄存器,全设置为1表示禁止上拉功能

mov r2, #0x00

str r2, [r1, #0x4] //0x56000054是GPFDAT,总共8位有效,每位控制一个引脚,主要是将GPF4-GPF7数据位全设置为0而这四个引脚是用来控制板子上4个LED,置0则表示亮。

# 230 \ @ set GPIO for UART mov r1, #0x56000000

add r1, r1, #0x70 //0x56000070为GPHCON 用来配置port H 而port H主要就是来控制UART的各个引脚如:UART中接收和发送端口RXDn和TXDn,当然还有自动流控制模式的nRTS0和nCTS0端口。

ldr r2, gpio_con_uart //我们可以看到 gpio_con_uart: .long vGPHCON gpio_up_uart: .long vGPHUP 而在platform中的smdk2410.h中定义了这两个值#define vGPHCON 0x0016faaa 表示GPHCON控制11个引脚,如GPH2若设置为10则表示TXD0.类似,具体的查看数据手册

#define vGPHUP 0x000007ff //同样将这11位的引脚上拉禁止 str r2, [r1, #0x0] ldr r2, gpio_up_uart

str r2, [r1, #0x8] //上面也是来配置串口所用到的GPIO口,因为串口的输入输出口都是利用到GPIO bl InitUART

# 259 \ bl copy_myself

@ jump to ram ldr r1, =on_the_ram add pc, r1, #0

17

nop nop

1: b 1b @ infinite loop

on_the_ram:

# 279 \

@ get read to call C functions开始调用C函数之前就需要将一些参数准备好,如堆栈要准备好函数调用时需要进出栈

ldr sp, DW_STACK_START @ setup stack pointer mov fp, #0 @ no previous frame, so fp=0 mov a2, #0 @ set argv to NULL

bl main @ call main

mov pc, #0x00000000 @ otherwise, reboot @

@ End VIVI head @

下面是子例程 @

@ Wake-up codes @

WakeupStart:

@ Clear sleep reset bit ldr r0, PMST_ADDR mov r1, #(1 << 1) str r1, [r0]

@ Release the SDRAM signal protections ldr r0, PMCTL1_ADDR ldr r1, [r0]

bic r1, r1, #((1 << 19) | (1 << 18) | (1 << 17)) str r1, [r0]

@ Go...

ldr r0, PMSR0_ADDR @ read a return address ldr r1, [r0] mov pc, r1 nop nop

1: b 1b @ infinite loop

SleepRamProc:

18

@ SDRAM is in the self-refresh mode */ ldr r0, REFR_ADDR ldr r1, [r0]

orr r1, r1, #(1 << 22) str r1, [r0]

@ wait until SDRAM into self-refresh mov r1, #16 1: subs r1, r1, #1 bne 1b

@ Set the SDRAM singal protections ldr r0, PMCTL1_ADDR ldr r1, [r0]

orr r1, r1, #((1 << 19) | (1 << 18) | (1 << 17)) str r1, [r0]

ldr r0, PMCTL0_ADDR ldr r1, [r0]

orr r1, r1, #(1 << 3) str r1, [r0] 1: b 1b

# 379 \

.globl memsetup; .align 0; memsetup: //对ENTRY(memsetup)宏的展开 @ initialise the static memory

//同样在这里是对内存控制中用到的13个寄存器进行初始化 @ set memory control registers mov r1, #0x48000000 adrl r2, mem_cfg_val add r3, r1, #52 1: ldr r4, [r2], #4

str r4, [r1], #4 cmp r1, r3 bne 1b

mov pc, lr @

@ copy_myself: copy vivi to ram

@下面的将vivi从flash中搬移到sdram中来执行,很重要 copy_myself:

mov r10, lr //将返回地址保存到r10,为了执行完后返回到主程序

19

@ reset NAND

mov r1, #0x4E000000

ldr r2, =0xf830 @ initial value 初始化NFCONF寄存器,至于为什么设置为0xf830前面在NAND里面讲过 str r2, [r1, #0x00] ldr r2, [r1, #0x00]

bic r2, r2, #0x800 @ enable chip 也就是相当于NFCONF &= ~0x800 将第15为置为1使能NAND FLASH str r2, [r1, #0x00]

mov r2, #0xff @ RESET command

strb r2, [r1, #0x04] //向NFCMD寄存器中置0xff也就是reset命令

mov r3, #0 @ wait //下面几句是一个延时程序用来等待几秒,等到NAND 准备好 1: add r3, r3, #0x1 cmp r3, #0xa blt 1b

2: ldr r2, [r1, #0x10] @ wait ready

tst r2, #0x1 //检查NFSTAT寄存器的第0位是否为1也就是是否准备好 beq 2b //没有准备好则继续等待 ldr r2, [r1, #0x00]

orr r2, r2, #0x800 @ disable chip 相当于NFCONF |= 0x800 禁止掉NAND FLASH等到要使用FLASH时再使能 str r2, [r1, #0x00]

@ get read to call C functions (for nand_read())

ldr sp, DW_STACK_START @ setup stack pointer //每次需要从汇编调到C函数时 都需要设置好堆栈

mov fp, #0 @ no previous frame, so fp=0

@ copy vivi to RAM 之前都是一些初始化,下面才开始利用C函数来真正开始拷贝

ldr r0, =(0x30000000 + 0x04000000 - 0x00100000)//DRAM_BASE + DRAM_SIZE - VIVI_RAM_SIZE,为什么这里不是将vivi拷贝到sdram的起始地址而是拷贝到64MB的sdram的最后1M的区域,可能是这里的sdram采用高端模式,将映象从高地址向低地址存放

mov r1, #0x0 //r1则是vivi的起始地址,也就是flash上的0x0地址

mov r2, #0x20000 //上面三句都是用来为调用nand_read_ll函数准备好参数,r2表示拷贝大小

bl nand_read_ll //这个c函数在arch/s3c2410/nand_read.c中 nand_read_ll就不具体分析了在nand里面有讲过

tst r0, #0x0 //为什么要比较r0与上0,等于0的话 则去执行ok_nand_read,在这里r0是nand_read_ll函数的返回值,拷贝成功则返回0,所以这就是为什么将r0和0比较 beq ok_nand_read //ok_nand_read子程序用来比较拷贝到sdram最后1MB的vivi和原始的存放在flash上的vivi是否相同,检查拷贝是否成功,vivi在sdram中的位置也就是离0x34000000地址前1MB的位置也就是0x33f00000 # 441 \

20

ok_nand_read:

@ verify

mov r0, #0 //r0这里为flash上vivi的起始地址

ldr r1, =0x33f00000 //r1这里为拷贝到sdram上vivi的起始地址 mov r2, #0x400 @ 4 bytes * 1024 = 4K-bytes //要比较多少个字节数 go_next:

ldr r3, [r0], #4 //将r0对应地址的值取出来保存到r3中,然后r0自加4个字节 ldr r4, [r1], #4

teq r3, r4 //测试r3和r4是否相等

bne notmatch //若不相等,则跳到notmarch处

subs r2, r2, #4 //将比较总字节数减去4个字节,已经比较了4个字节

beq done_nand_read //若r2为0,则表示已经比较完毕,跳转到done_nand_read处 bne go_next //r2若还不等于0则继续取值比较 notmatch:

# 469 \1: b 1b

done_nand_read:

mov pc, r10 //比较完了 就退出子程序,返回主程序执行

@ clear memory @ r0: start address @ r1: length mem_clear:

mov r2, #0 mov r3, r2 mov r4, r2 mov r5, r2 mov r6, r2 mov r7, r2 mov r8, r2 mov r9, r2

clear_loop:

stmia r0!,{r2-r9} //将r2-r9也就是0赋值给从r0为内存起始地址的连续的8*4个字节中

subs r1, r1, #(8 * 4) //每次清除32个字节 bne clear_loop

mov pc, lr

# 613 \@ Initialize UART @

@ r0 = number of UART port

21

InitUART: //这里也不细讲了,在UART章节中已经详细的讲解了每个寄存器的设置 ldr r1, SerBase mov r2, #0x0 str r2, [r1, #0x08] str r2, [r1, #0x0C] mov r2, #0x3 str r2, [r1, #0x00] ldr r2, =0x245 str r2, [r1, #0x04]

mov r2, #((50000000 / (115200 * 16)) - 1) str r2, [r1, #0x28]

mov r3, #100 mov r2, #0x0 1: sub r3, r3, #0x1 tst r2, r3 bne 1b

# 653 \ mov pc, lr @

@ Exception handling functions @

HandleUndef:

1: b 1b @ infinite loop

HandleSWI:

1: b 1b @ infinite loop

HandlePrefetchAbort: 1: b 1b @ infinite loop

HandleDataAbort: 1: b 1b @ infinite loop

HandleIRQ:

1: b 1b @ infinite loop

HandleFIQ:

1: b 1b @ infinite loop

HandleNotUsed:

1: b 1b @ infinite loop

22

@

@ Low Level Debug @

# 838 \@

@ Data Area @

@ Memory configuration values .align 4

mem_cfg_val: //这些变量都是内存控制寄存器的初始值,因为寄存器比较多,所以将初始值制作成表的形式,然后分别读表来初始化各个寄存器 .long 0x22111110 .long 0x00000700 .long 0x00000700 .long 0x00000700 .long 0x00000700 .long 0x00000700 .long 0x00000700 .long 0x00018005 .long 0x00018005 .long 0x008e0459 .long 0xb2 .long 0x30 .long 0x30

@ Processor clock values .align 4

clock_locktime:

.long 0x00ffffff mpll_50mhz:

.long ((0x5c << 12) | (0x4 << 4) | (0x2))

mpll_200mhz:

.long ((0x5c << 12) | (0x4 << 4) | (0x0)) clock_clkcon:

.long 0x0000fff8 clock_clkdivn: .long 0x3

@ initial values for serial uart_ulcon:

.long 0x3 uart_ucon:

.long 0x245 uart_ufcon:

.long 0x0 uart_umcon:

23

.long 0x0

@ inital values for GPIO gpio_con_uart:

.long 0x0016faaa gpio_up_uart:

.long 0x000007ff

.align 2

DW_STACK_START:

.word (((((0x30000000 + 0x04000000 - 0x00100000) - 0x00100000) - 0x00004000) - (0x00004000 + 0x00004000 +

0x00004000)) - 0x00008000)+0x00008000 -4 # 922 \.align 4 SerBase:

.long 0x50000000 # 935 \.align 4

PMCTL0_ADDR:

.long 0x4c00000c PMCTL1_ADDR:

.long 0x56000080 PMST_ADDR:

.long 0x560000B4 PMSR0_ADDR:

.long 0x560000B8 REFR_ADDR:

.long 0x48000024 [root@lqm vivi_myboard]#

2.2.3中断向量表

开始对中断向量表很疑惑。现在的理解比较清晰了,在硬件实现上,会支持中断机制,这个可以参考微机接口原理部分详细理解。现在的中断机制处理的比较智能,对每一类中断会固定一个中断向量,比如说,发生IRQ中断,中断向量地址为0x00000018(当然,这还与ARM9TDMI core有关,其中一个引脚可以把中断向量表配置为高端启动,或者低端启动。你可以通过CP15的register 1的bit 13的V bit来设置,可以查看Datasheet TABLE 2-10来解决。),那么PC要执行的指令就是0x00000018。如果在这个地址放上一个跳转指令(只能使用b或者ldr),那么就可以跳到实际功能代码的实现区了。ARM体系结构规定在上电复位后的起始位置,必须有8条连续的跳转指令,这是bootloader的识别入口,通过硬件实现。看一下vivi的中断向量表:

@ 0x00

24

b Reset @ 0x04

HandleUndef:

b HandleUndef @ 0x08 HandleSWI:

b HandleSWI @ 0x0c

HandlePrefetchAbort:

b HandlePrefetchAbort @ 0x10

HandleDataAbort:

b HandleDataAbort @ 0x14

HandleNotUsed:

b HandleNotUsed @ 0x18 HandleIRQ:

b HandleIRQ @ 0x1c HandleFIQ:

b HandleFIQ

注意,中断向量表可以修改,也可以通过MMU实现地址重映射,来改变对应的物理介质(在MMU一章中我们提到过由于flash介质上执行速度比较慢,所以我们一般利用MMU将0x0开始的一段代码映射到SDRAM从0x30000000开始的区域,这样代码的执行就比较快)。如果不对每种中断进行测试,可以采用下面的书写方式。

@ 0x00

b Reset @ 0x04

HandleUndef: b . @ 0x08 HandleSWI: b . @ 0x0c

HandlePrefetchAbort: b . @ 0x10

HandleDataAbort: b . @ 0x14

HandleNotUsed: b .

25

int mtd_init(void) {

int ret;

#ifdef CONFIG_MTD_CFI ret = cfi_init(); #endif

#ifdef CONFIG_MTD_SMC ret = smc_init(); #endif

#ifdef CONFIG_S3C2410_AMD_BOOT ret = amd_init(); #endif

可见,vivi现在支持三种类型的存储接口,一种是CFI,也就是Intel发起的一个flash的接口标准,主要就是intel的nor flash系列;一种是smc,智能卡系列接口,nand flash就是通过这个接口实现读写的;一种是AMD的flash系列。选择什么启动方式,就要选择相应的配置项。

核心部分根据配置应该调用smc_init函数。-->【drivers/mtd/maps/s3c2410_flash.c】。这里最为核心的就是两个数据结构,一个是mtd_info,位于【include/mtd/mtd.h】,如下: struct mtd_info { u_char type; u_int32_t flags;

u_int32_t size; // Total size of the MTD

/* \飗e users may take this * to be the only erase size available, or may use the more detailed * information below if they desire */

u_int32_t erasesize;

u_int32_t oobblock; // Size of OOB blocks (e.g. 512)

u_int32_t oobsize; // Amount of OOB data per block (e.g. 16) u_int32_t ecctype; u_int32_t eccsize;

// Kernel-only stuff starts here. char *name; int index;

/* Data for variable erase regions. If numeraseregions is zero, * it means that the whole device has erasesize as given above. */

int numeraseregions;

struct mtd_erase_region_info *eraseregions;

/* This really shouldn't be here. It can go away in 2.5 */ u_int32_t bank_size; struct module *module;

36

int (*erase) (struct mtd_info *mtd, struct erase_info *instr); /* This stuff for eXecute-In-Place */

int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf); /* We probably shouldn't allow XIP if the unpoint isn't a NULL */ void (*unpoint) (struct mtd_info *mtd, u_char * addr);

int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);

int (*read_ecc) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *eccbuf);

int (*write_ecc) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf, u_char *eccbuf);

int (*read_oob) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); int (*write_oob) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf); /*

* Methods to access the protection register area, present in some * flash devices. The user data is one time programmable but the * factory data is read only. */

int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);

int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);

/* This function is not yet implemented */

int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);

/* Chip-supported device locking */

int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len); int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len); void *priv; };

mtd_info是表示MTD设备的结构,每个分区也被表示为一个mtd_info,如果有两个MTD设备,每个设备有三个分区,那么在系统中就一共有6个mtd_info结构。关于mtd_info,在《Linux MTD源代码分析》中讲解非常透彻,不过需要注意的是,在vivi的实现中没有使用mtd_table,另外priv指向的是nand_chip,这些都是与Linux下不同的地方,主要是为了简化。另一个是nand_chip,这个结构则包含了nand flash的所有信息。 struct nand_chip {

#ifdef CONFIG_MTD_NANDY void (*hwcontrol)(int cmd); void (*write_cmd)(u_char val); void (*write_addr)(u_char val); u_char (*read_data)(void); void (*write_data)(u_char val); void (*wait_for_ready)(void);

37

/*spinlock_t chip_lock;*/ /*wait_queue_head_t wq;*/ /*nand_state_t state;*/ int page_shift; u_char *data_buf; u_char *data_cache; int cache_page;

struct nand_smc_dev *dev;

u_char spare[SMC_OOB_SIZE]; #else /* CONFIG_MTD_NANDY */ unsigned long IO_ADDR_R; unsigned long IO_ADDR_W; void (*hwcontrol)(int cmd); int (*dev_ready)(void); int chip_delay;

/*spinlock_t chip_lock;*/ /*wait_queue_head_t wq;*/ /*nand_state_t state;*/ int page_shift; u_char *data_buf; u_char *data_cache; int cache_page;

#ifdef CONFIG_MTD_NAND_ECC u_char ecc_code_buf[6]; u_char reserved[2]; #endif

#endif /* CONFIG_MTD_NANDY */ };

所谓的初始化,其实就是填充处理上述两个数据结构的过程。填充完毕之后,后续的工作都会基于此展开。下面开始看smc_init的代码。

mymtd = mmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip)); this = (struct nand_chip *)(&mymtd[1]);

在这里,第一句参考前面heap的实现代码,重点看第二句代码。这句代码是有一定的技巧性,但是也存在着很大的风险。其中,mymtd是指向struct mtd_info的指针,那么mymtd[1]实际上是等效于*(mymtd + 1)的数学计算模式,注意mymtd并非数组,这里仅仅利用了编译器翻译的特点。对于指针而言,加1实际上增加的指针对应类型的值,在这里地址实际上增加了sizeof(struct mtd_info),因为前面分配了两块连续的地址空间,所以&(*(mymtd + 1))实际上就是mtd_info数据结构结束的下一个地址,然后实现强制转换,于是this就成为了nand_chip的入口指针了。但是,这里必须要把握好,因为这个地方是不会进行内存的检查的,也就是说,如果你使用了mymtd[2],那么仍然按照上述公式解析,虽然可以运算,可是就是明显的指针泄漏了,可能会出现意料不到的结果。写了一个测试程序,对这点进行了探讨,要小心内存问题。

38

了解清楚了,mymtd指向mtd_info的入口,this指向nand_chip的入口。

memset((char *)mymtd, 0, sizeof(struct mtd_info)); memset((char *)this, 0, sizeof(struct nand_chip));

mymtd->priv = this;

上述代码首先初始化这两个结构体,即均为0.然后利用priv把二者联系起来,也就是mymtd通过其成员priv指向this,那么mymtd中的抽闲操作函数,比如read、write等,真正的是通过this来实现的。很明显,this的实现部分属于flash硬件驱动层,而mymtd部分则属于MTD设备层,二者的联系就是通过成员priv实现的。

接下来首先是初始化nand flash设备,这跟前面的基础实验一致。

/* set NAND Flash controller */ nfconf = NFCONF;

/* NAND Flash controller enable */ nfconf |= NFCONF_FCTRL_EN;

/* Set flash memory timing */

nfconf &= ~NFCONF_TWRPH1; /* 0x0 */ nfconf |= NFCONF_TWRPH0_3; /* 0x3 */ nfconf &= ~NFCONF_TACLS; /* 0x0 */

NFCONF = nfconf;

然后填充nand flash的数据结构的一个实例this,分成了两个部分,nand flash基本操作函数成员的初始化、其余信息的填写。

/* Set address of NAND IO lines */ this->hwcontrol = smc_hwcontrol; this->write_cmd = write_cmd; this->write_addr = write_addr; this->read_data = read_data; this->write_data = write_data;

this->wait_for_ready = wait_for_ready;

/* Chip Enable -> RESET -> Wait for Ready -> Chip Disable */ this->hwcontrol(NAND_CTL_SETNCE); this->write_cmd(NAND_CMD_RESET); this->wait_for_ready();

this->hwcontrol(NAND_CTL_CLRNCE);

smc_insert(this);

上面这些都不难理解,感觉在结构体设计上还是比较出色的,把成员和相应的操作封

39

装起来,面向对象的一种方法。下面看smc_insert,根据刚才填充的nand_flash结构,构造mtd_info结构无非还是按照结构体填写相应的信息,细节部分就不深入探讨了。

inline int

smc_insert(struct nand_chip *this) {

/* Scan to find existance of the device */ if (smc_scan(mymtd)) { return -ENXIO; }

/* Allocate memory for internal data buffer */ this->data_buf = mmalloc(sizeof(u_char) *

(mymtd->oobblock + mymtd->oobsize));

if (!this->data_buf) {

printk(\ this->data_buf = NULL; return -ENOMEM; }

return 0; }

第一部分扫描填充mymtd数据结构。后面主要用于nand flash的oob缓冲处理。具体部分可以参考《s3c2410完全开发》。我们先来看看smc_scan函数的执行(drivers/mtd/nand/smc_core.c这个文件中包含的是nand flash中绝大多数真正进行操作的函数):

int smc_scan(struct mtd_info *mtd) {

int i, nand_maf_id, nand_dev_id; //定义flash的厂家ID和设备ID

struct nand_chip *this = mtd->priv; //获得与mtd设备相联系的真正的flash设备结构 /* Select the device */ nand_select(); //#define nand_select() this->hwcontrol(NAND_CTL_SETNCE); nand_command(mtd, NAND_CMD_RESET, -1, -1); udelay(10);这三句代码和我们之前在nand章节中讲解的片选flash是一个意思,先将使能nand的那位置1也就是片选nand,然后想nand发送reset命令,然后等待一段时间。 /* Send the command for reading device ID */

nand_command(mtd, NAND_CMD_READID, 0x00, -1);//向nand发送读ID的命令 this->wait_for_ready(); //等待nand结果数据准备好 /* Read manufacturer and device IDs */

nand_maf_id = this->read_data();//nand数据准备好后,通过read_data可以相继的读出厂家ID和设备ID

nand_dev_id = this->read_data();

/* Print and sotre flash device information */

for (i = 0; nand_flash_ids[i].name != NULL; i++) { //在数组nand_flash_ids中查找与ID相符合的项,可以看到下面对数组说明

40

本文来源:https://www.bwwdw.com/article/1e9g.html

Top