• 移植u-boot到S3C2440之从内存启动


    本文将uboot移植到S3C2440,略过最麻烦的需要计算的ROM移植部分,直接从内存启动S3C2440。

    文章目录


    一、前置知识

    1. Linux可执行文件介绍

    我们可以把可执行文件分为2种情况:存放态和运行态。

    存放态:

    可执行文件经过烧到存储介质上(flash或磁盘)的分布,此时可执行文件通常有2部分组成,代码段和数据段,代码段又分为可执行代码段 (.text)和只读数据段(.rodata),数据段可以分为初始化数据段(.data)和未初始化代码段(.bss),如下:

    ±------------±----------
    | .bss>>>>| (ZI)
    ±------------±----------数据段
    | .data>>> | (RW)
    ±------------±----------
    | .rodata >|
    |------------------------| 代码段(RO)
    | .text >>>>|
    ±------------±----------

    • .text代码段
    • .rodata只读数据段
    • .bss是未初始化全局变量段
    • .data是初始化被赋值的全局变量

    运行态:

    可执行文件经过装载后就变成为运行态,当可执行文件装载后, 在RAM中的分布如下:
    | -------------|
    ±--------------±- ZI段结束地址
    | ZI 段>>>>|
    ±--------------±- ZI段起始地址
    | 保留区2>>|
    ±--------------±- RW段结束地址
    | RW 段>>>|
    ±--------------±- RW段起始地址
    | 保留区1>>|
    ±--------------±- RO段结束地址
    | RO 段>>>|
    ±--------------±- RO段起始地址

    • ZI段主要是未初始化数据
    • RW主要是自动变量
    • RO主要是代码段。

    ELF

    elf文件是UNIX系统实验室开发的,主要包括可执行文件,可充分定位文件与可共享库文件等。按功能分又可分为链接文件和可执行文件。一个elf文件可以使用binutils工具集里面的readelf来查看,比如readelf -h u-boot查看u-boot文件头。

    ELF Header:
      Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF32
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              DYN (Shared object file)
      Machine:                           ARM
      Version:                           0x1
      Entry point address:               0x0
      Start of program headers:          52 (bytes into file)
      Start of section headers:          1835468 (bytes into file)
      Flags:                             0x5000000, Version5 EABI
      Size of this header:               52 (bytes)
      Size of program headers:           32 (bytes)
      Number of program headers:         4
      Size of section headers:           40 (bytes)
      Number of section headers:         25
      Section header string table index: 22
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2. binutils工具集

    binutils是一组二进制工具集,它包括addr2line、ar、gprof、nm、objcopy、objdumpr、ranlib、size、strings、strip等。

    ar

    ar用于建立、修改、提取库文件。ar至少需要两个参数才能运行,比如:

    $ ar rv libtest.a add.o minus.o
    
    • 1

    是指将add.o、minus.o做成库文件libtest.a。其中,r是指将文件列表插入归档文件,v是指得到操作版本号。
    这样我们引用库文件的时候就可以使用:

    $ gcc -o test test.c -ltest
    
    • 1

    nm

    nm软件的作用是现实目标文件的信息和属性,比如:

    $ nm test.o
             U Add
    00000000 T main
             U Minus
             U printf
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里U标志符号未被定义,T表示符号位于代码段,D表示符号位于已初始化数据部分,还有B、t、r、b、R、A、W、d等等。

    objcopy

    objcopy软件用来将某种格式的目标软件转换成另一种格式的目标软件。
    比如u-boot使用本软件将u-boot转换成u-boot.srec格式。

    objdump

    本软件可以用来进行反汇编和查看目标文件信息。

    ld

    ld软件用来吧各种目标文件和库文件链接在一起,定位数据和函数的地址,最终生成可执行文件。

    od & xd

    od (octal dump)和 xd(hexdump)命令可以以十进制、八进制、十六进制和ASCII码来显示文件或者流,它们对于访问或可视地检查文件中不能直接显示在终端上的字符很有用。
    语法:od [选项] 文件…
    命令中各选项的含义:

    • A 指定地址基数,包括:
      d 十进制
      o 八进制(系统默认值)
      x 十六进制
      n 不打印位移值
    • t 指定数据的显示格式,主要的参数有:
      c ASCII字符或反斜杠序列
      d 有符号十进制数
      f 浮点数
      o 八进制(系统默认值为02)
      u 无符号十进制数
      x 十六进制数
      除了选项c以外的其他选项后面都可以跟一个十进制数n,指定每个显示值所包含的字节数。

    说明:od命令系统默认的显示方式是八进制,这也是该命令的名称由来(Octal Dump)。但这不是最有用的显示方式,用ASCII码和十六进制组合的方式能提供更有价值的信息输出。
    od -h 文件名

    $ od -c file-hole
    0000000   a   b   c   d   e   f   g   h   i   j  /0  /0  /0  /0  /0  /0
    0000020  /0  /0  /0  /0  /0  /0  /0  /0  /0  /0  /0  /0  /0  /0  /0  /0
    *
    0040000   A   B   C   D   E   F   G   H   I   J
    0040012
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    链接描述文件

    链接描述文件用于显式的控制ld的链接过程。ld的“-T”选项可以指定链接描述文件的名称。

    链接描述文件遵循特定的链接命令语言——linker scripts的语法。比如u-boot.lds

    3. Makefile

    腾讯云的一篇文章《Linux makefile使用基础》也算简单介绍了Makefile的知识。

    DENX的u-boot是一个狠庞大的系统,研究透彻u-boot的Make的运作同样是一个狠庞大的工程,目前我仅参考以上两个参考文档对S3C2440的u-boot的编译的Make相关的文件和命令做一个简单说明。如果深入学习《嵌入式Linux应用开发完全手册》是一个很好的指引。

    一般针对S3C2440的移植都是参照smdk2410来的,所以在smdk2410的目录,拷贝一个做为smdk2440;

    另外,编译后,可以直接查找*.o文件查看已编译选项;

    读Makefile可以发现u-boot.lds的用途。

    start.S是整个uboot的起始文件。

    以上四项《嵌入式Linux应用开发完全手册》已经从源头说明了其组成原理。

    make smdk2440_config的解析:

    一般的,在配置u-boot的时候,我们会输入:

    make smdk2440_config
    
    • 1

    从早期的u-boot版本中可以见到这样的几行:

    MKCONFIG=$(SRCTREE)/mkconfig
    
    
    smdk2410_config :     unconfig
    
            @$(MKCONFIG) smdk2410 arm arm920t smdk2410 NULL s3c24x0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    由此翻译过来,make smdk2440_config就相当于:

    ./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0
    
    • 1

    ./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0的解析

    这个mkconfig是一个shell脚本,它根据传入的参数,做了如下工作:

    ln -s asm-$2 asm
    ln -s arm-$6 asm-$2/arch
    ln -s proc-armv arm-$2/proc
    
    • 1
    • 2
    • 3

    创建config.mk文件

    ARCH = arm
    CPU = arm920t
    BOARD = smdk2410
    SOC = s3c24x0
    
    • 1
    • 2
    • 3
    • 4

    创建于开发板相关的config.h文件

    #include <configs/$1.h>
    
    • 1

    4. uboot.lds分析与解析

    u-boot中.lds连接脚本文件的分析基于S3C2440的U-BOOT的start.S分析对这部分有详细说明,这里摘录部分重要知识:

    OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm"")
        ;指定输出可执行文件是elf格式,32位ARM指令,小端
    OUTPUT_ARCH(arm)
        ;指定输出可执行文件的平台为ARM
    ENTRY(_start)
        ;指定输出可执行文件的起始代码段为_start.
    SECTIONS
    {
            . = 0x00000000 ; 指明目标代码的起始地址从0x0位置开始,"."代表的是当前位置
            . = ALIGN(4)   ; 代码以4字节对齐
            .text : ;指定代码段
            {
              cpu/arm920t/start.o (.text) ; 代码的第一个代码部分,指明start.s是入口程序代码,被放到代码段的开头
              *(.text) ;其它代码部分
            }
            . = ALIGN(4)
            .rodata : { *(.rodata) } ;指定只读数据段,RO段
            . = ALIGN(4);
            .data : { *(.data) }     ;指定读/写数据段,RW段
            . = ALIGN(4);
            .got : { *(.got) }       ;指定got段, got段式是uboot自定义的一个段, 非标准段
            __u_boot_cmd_start = .   ;把__u_boot_cmd_start赋值为当前位置, 即起始位置
            .u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在该段.
            __u_boot_cmd_end = .;把__u_boot_cmd_end赋值为当前位置,即结束位置
            . = ALIGN(4);
            __bss_start = .; 把__bss_start赋值为当前位置,即bss段的开始位置
            .bss : { *(.bss) }; 指定bss段
            _end = .; 把_end赋值为当前位置,即bss段的结束位置
    }
    
    • 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

    GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特殊指明,由C源程序生成的目标代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符串;.bss(未初始化数据段)包含未初始化的变量、数组等。GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特殊指明,由C源程序生成的目标代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符串;.bss(未初始化数据段)包含未初始化的变量、数组等。连接器的任务就是将多个目标文件的.text、.data和.bss等段连接在一起,而连接脚本文件是告诉连接器从什么地址开始放置这些段。

    .lds文件形式的完整描述

    SECTIONS {
    ...
    secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
      { contents } >region :phdr =fill
    ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    secname和contents是必须的,其他的都是可选的。下面挑几个常用的看看:

    • secname:段名
    • contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
    • start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。
    • AT(ldadr):定义本段存储(加载)的地址。

    示例如下:

    ENTRY(begin)
    SECTION
    { .=0x00300000;
    .text : { *(.text) }
    .data: { *(.data) }
    .bss: { *(.bss) }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中,ENTRY(begin)指明程序的入口点为begin标号;.=0x00300000指明目标代码的起始地址为0x00300000,这一段地址可以是SDRAM的起始地址;.text : { *(.text) }表示从0x00300000开始放置所有目标文件的代码段,随后的.data: { *(.data) }表示数据段从代码段的末尾开始,再后是.bss段。

    5. ARM寄存器说明

    ARM体系结构的寄存器R0~R15主要有三类:

    • 未分组寄存器R0~R7
    • 分组寄存器R8~R14
    • PC寄存器R15

    未分组寄存器

    同一个寄存器名在ARM微处理器内部只有一个独立的物理寄存器与之对应。

    分组寄存器

    每一个物理寄存器分别与不同的处理器模式相对应。对于R8~R12来说,每个寄存器对应两个不同的物理寄存器,当使用fiq模式时,访问寄存器R8_fiq~R12_fiq;当使用除fiq模式以外的其他模式时,访问寄存器R8_usr~R12_usr。对于于R13、R14来说,每个寄存器对应6个不同的物理寄存器,其中的一个是用户模式与系统模式共用,另外5个物理寄存器对应于其他5种不同的运行模式。

    寄存器R13

    在ARM指令中常用作堆栈指针,但这只是一种习惯用法

    R14

    也称作子程序链接寄存器(Subroutine Link Register)或连接寄存器LR。当执行BL子程序调用指令时,R14中得到R15的备份。

    R14寄存器使用举例:当用BL或BLX指令调用子程序时,将PC的当前值拷贝给R14,执行完子程序后,又将R14的值拷贝回PC,即可完成子程序的调用返回。

    子程序返回指令:MOV PC, LR   或者  BX LR
    将R14存入堆栈:STMFD SP!,{<Regs>,LR}
    子程序返回时的R14出栈:LDMFD SP!,{<Regs>,PC}
    
    • 1
    • 2
    • 3

    程序计数器PC(R15)

    PC在ARM模式下,位[1:0]为0,位[31:2]用于保存PC;在Thumb 状态下,位[0]为0,位[31:1]用于保存PC。(符合ARM微处理器字对齐规则:32位活16位指令长度,而存储是以字节为单位的)。对于ARM指令集而言,PC总是指向当前指令的下两条指令。即PC的值为当前指令的地址值加8个字节。

    寄存器R16

    寄存器R16用作当前程序状态寄存器CPSR

    条件码标志(Condition Code Flags)

    N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行。在ARM状态下,大多数的指令都是有条件执行的。

    • N 当用两个补码表示的带符号数进行运算时,N=1 表示运算的结果为负数;N=0 表示运算的结果为正数或零;
    • Z Z=1 表示运算的结果为零;Z=0 表示运算的结果为非零;
    • C 可以有 4 种方法设置 C 的值:
      • 加法运算(包括比较指令CMN):当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。
      • 减法运算(包括比较指令CMP):当运算时产生了借位(无符号数溢出),C=0,否则C=1。
      • 对于包含移位操作的非加/减运算指令,C为移出值的最后一位。
      • 对于其他的非加/减运算指令,C的值通常不改变。
    • V 可以有2种方法设置V的值:
      • 对于加/减法运算指令,当操作数和运算结果为二进制的补码表示的带符号数时,V=1表示符号位溢出。
      • 对于其他的非加/减运算指令,V的值通常不改变

    控制位

    PSR的低8位(包括I、F、T和M[4:0])称为控制位,当发生异常时这些位可以被改变。如果处理器运行特权模式,这些位可以由程序修改。

    • 中断禁止位I、F: I=1 禁止IRQ中断; F=1 禁止FIQ中断。
    • T标志位:该位反映处理器的运行状态。对于ARM体系结构v5及以上的版本的T系列处理器,当该位为1时,程序运行于Thumb状态,否则运行于ARM状态。对于ARM体系结构v5及以上的版本的非T系列处理器,当该位为1时,执行下一条指令为引起定义的指令异常,当该位为0时表示运行于ARM状态。
    • 运行模式位M[4:0]:M0、M1、M2、M3、M4是模式位。这些位决定了处理器的运行模式。

    保留位 & SPSR

    每一种运行模式下又都有一个专用的物理状态寄存器,称为SPSR(Saved Program Status Register,备份的程序状态寄存器),当异常发生时,SPSR用于保存CPSR的当前值,从异常退出时则可由SPSR来恢复CPSR。

    由于用户模式和系统模式不属于异常模式,他们没有SPSR,当在这两种模式下访问SPSR,结果是未知的。

    二、部分需要掌握的ARM指令

    ARM指令集是加载/存储型的,就是说指令集仅仅能处理寄存器中的数据,而且处理结果都要放回寄存器中,而对系统存储器的访问则需要通过专门的加载/存储指令来完成。

    ARM微处理器的指令集可以分为六大类:跳转指令、数据处理指令、程序状态寄存器(PSR)处理指令、加载/存储指令、协处理器指令和异常产生指令。

    指令的条件域

    当处理器工作在ARM状态时,几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件的执行。当指令的执行条件满足时,指令被执行,否则指令被忽略。每一条ARM指令包含4位的条件码,位于指令的最高4位[31:28]。条件码共有16种,每种条件码可用两个字符表示,这两个字符可以添加在指令助记符的后面和指令同时使用。

    例如,跳转指令B可以加上后缀EQ变为BEQ表示“相等则跳转”,即当CPSR中的Z标志置位时发生跳转。

    在16种条件标志码中,只有15种可以使用。

    ARM 指令的寻址方式

    立即寻址

    立即寻址也叫立即数寻址,这是一种特殊的寻址方式,操作数本身就在指令中给出,只要取出指令也就取到了操作数。

    寄存器寻址

    寄存器寻址就是利用寄存器中的数值作为操作数

    寄存器间接寻址

    寄存器间接寻址就是以寄存器中的值作为操作数的地址,而操作数本身存放在存储器中。

    基址变址寻址

    基址变址寻址就是将寄存器(该寄存器一般称作基址寄存器)的内容与指令中给出的地址偏移量相加,从而得到一个操作数的有效地址。变址寻址方式常用于访问某基地址附近的地址单元。例:

    LDR R0,[R1,#4] ;R0←[R1+4]
    LDR R0,[R1,#4]! ;R0←[R1+4]、R1←R1+4
    LDR R0,[R1] ,#4 ;R0←[R1]、R1←R1+4
    LDR R0,[R1,R2] ;R0←[R1+R2]
    
    • 1
    • 2
    • 3
    • 4

    多寄存器寻址

    采用多寄存器寻址方式,一条指令可以完成多个寄存器值的传送。这种寻址方式可以用一条指令完成传送最多 16 个通用寄存器的值。例

    LDMIA R0,{R1,R2,R3,R4}
    ;R1←[R0]
    ;R2←[R0+4]
    ;R3←[R0+8]
    ;R4←[R0+12]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    相对寻址

    与基址变址寻址方式相类似,相对寻址以程序计数器 PC 的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。

    BL  NEXT      ;跳转到子程序 NEXT 处执行
    
    • 1

    堆栈寻址

    ARM 微处理器支持四种类型的堆栈工作方式,即:

    • 满递增堆栈:堆栈指针指向最后压入的数据,且由低地址向高地址生成。
    • 满递减堆栈:堆栈指针指向最后压入的数据,且由高地址向低地址生成。
    • 空递增堆栈:堆栈指针指向下一个将要放入数据的空位置,且由低地址向高地址生成。
    • 空递减堆栈:堆栈指针指向下一个将要放入数据的空位置,且由高地址向低地址生成。

    ARM指令类型

    跳转指令

    跳转指令用于实现程序流程的跳转,在 ARM 程序中有两种方法可以实现程序流程的跳转:

    • 使用专门的跳转指令。
    • 直接向程序计数器 PC 写入跳转地址值。

    ARM 指令集中的跳转指令可以完成从当前指令向前或向后的 32MB 的地址空间的跳转,包括以下 4 条指令:

    • B 跳转指令
    • BL 带返回的跳转指令
    • BLX 带返回和状态切换的跳转指令
    • BX 带状态切换的跳转指令

    B{条件} 目标地址

    B指令是最简单的跳转指令。注意存储在跳转指令中的实际值是相对当前PC值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址) 它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后 32MB的地址空间)。

    BL{条件} 目标地址

    BL 是另一个跳转指令,但跳转之前,会在寄存器R14 中保存PC的当前内容,因此,可以通过将R14 的内容重新加载到PC中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。例:

    BL    Label    ;当程序无条件跳转到标号 Label 处执行时,同时将当前的 PC 值保存到 R14 中。
    
    • 1

    BLX 指令

    BLX指令从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态有ARM状态切换到Thumb状态。该指令同时将PC的当前内容保存到寄存器R14 中。

    BX 指令

    BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。

    数据处理指令

    数据处理指令可分为数据传送指令、算术逻辑运算指令和比较指令等。数据处理指令包括:MOV 数据传送指令、MVN 数据取反传送指令、CMP 比较指令、CMN 反值比较指令、 TST 位测试指令、 TEQ 相等测试指令、 ADD 加法指令、 ADC 带进位加法指令、 SUB 减法指令、 SBC 带借位减法指令、 RSB 逆向减法指令、 RSC 带借位的逆向减法指令、 AND 逻辑与指令、 ORR 逻辑或指令、 EOR 逻辑异或指令、 BIC 位清除指令。

    MOV{条件}{S} 目的寄存器,源操作数

    MOV 指令将源操作数加载到目的寄存器。其中 S 选项决定指令的操作是否影响 CPSR 中条件标志位的值,当没有 S 时指令不更新 CPSR中条件标志位的值。例:

    MOV R1,R0,LSL#3 ;        将寄存器 R0 的值左移 3 位后传送到 R1。
    
    • 1

    MVN{条件}{S} 目的寄存器,源操作数

    MOV 指令不同之处是在传送之前将源操作数按位被取反。例:

    MVN R0, #0;        将立即数 0 取反传送到寄存器 R0 中,完成后 R0=-1。
    
    • 1

    CMP{条件} 操作数 1,操作数 2

    比较操作数1和操作数2,更新CSPR不保存结果。标志位表示的是操作数 1 与操作数 2 的关系(大、小、相等)。例如,当操作数 1 大于操作操作数 2,则此后的有GT 后缀的指令将可以执行。例:

    CMP  R1,#100        ;将寄存器 R1 的值与立即数 100 相减,并根据结果设置 CPSR 的标志位
    
    • 1

    CMN{条件} 操作数 1,操作数 2

    实际完成操作数 1 和操作数 2 相加,并根据结果更改条件标志位。例:

    CMN  R1,R0        ;将寄存器 R1 的值与寄存器 R0 的值相加,并根据结果设置 CPSR
    
    • 1

    TST{条件} 操作数 1,操作数 2

    TST指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值。操作数 1 是要测试的数据,而操作数 2 是一个位掩码,该指令一般用来检测是否设置了特定的位。例:

    TST R1,#0xffe         ;将寄存器 R1 的值与立即数 0xffe 按位与,并根据结果设置 CPSR
    
    • 1

    TEQ{条件} 操作数 1,操作数 2

    TEQ指令用于 把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的异或运算,并根据运算结果更新CPSR中条件标志位的值。该指令通常用于比较操作数 1 和操作数2 是否相等。例:

    TEQ  R1,R2        ;将寄存器 R1 的值与寄存器 R2 的值按位异或,并根据结果设置 CPSR
    
    • 1

    ADD{条件}{S} 目的寄存器,操作数 1,操作数 2

    ADD R0,R2,R3,LSL#1 ; R0 = R2 + (R3 << 1)
    
    • 1

    ADC{条件}{S} 目的寄存器,操作数 1,操作数 2

    ADC指令用于把两个操作数相加,再加上CPSR中的C条件标志位的值,并将结果存放到目的寄存器中。它使用一个进位标志位,这样就可以做比 32 位大的数的加法,注意不要忘记设置S后缀来更改进位标志。以下指令序列完成两个 128 位数的加法,第一个数由高到低存放在寄存器 R7~R4,第二个数由高到低存放在寄存器 R11~R8,运算结果由高到低存放在寄存器 R3~R0:

    ADDS R0,R4,R8 ; 加低端的字
    ADCS R1,R5,R9 ; 加第二个字,带进位
    ADCS R2,R6,R10 ; 加第三个字,带进位
    ADC R3,R7,R11; 加第四个字,带进位
    
    • 1
    • 2
    • 3
    • 4

    SUB{条件}{S} 目的寄存器,操作数 1,操作数 2

    SUB指令用于把操作数 1 减去操作数 2,并将结果存放到目的寄存器中。

    SUB R0,R2,R3,LSL#1         ; R0 = R2 - (R3 << 1)
    
    • 1

    SBC{条件}{S} 目的寄存器,操作数 1,操作数 2

    SBC指令用于把操作数 1 减去操作数 2,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。

    SUBS R0,R1,R2        ; R0 = R1 - R2 - !C,并根据结果设置 CPSR 的进位标志位
    
    • 1

    RSB{条件}{S} 目的寄存器,操作数 1,操作数 2

    RSB指令称为逆向减法指令,用于把操作数 2 减去操作数 1,并将结果存放到目的寄存器中。

    RSB R0,R2,R3,LSL#1         ; R0 = (R3 << 1) - R2
    
    • 1

    RSC{条件}{S} 目的寄存器,操作数 1,操作数 2

    RSC指令用于把操作数 2 减去操作数 1,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。

    AND{条件}{S} 目的寄存器,操作数 1,操作数 2

    AND指令用于在两个操作数上进行逻辑与运算,并把结果放置到目的寄存器中。该指令常用于屏蔽操作数 1 的某些位。

    AND  R0,R0,#3        ; 该指令保持 R0 的 0、1 位,其余位清零。
    
    • 1

    ORR{条件}{S} 目的寄存器,操作数 1,操作数 2

    该指令常用于设置操作数 1 的某些位。

    EOR{条件}{S} 目的寄存器,操作数 1,操作数 2

    该指令常用于反转操作数 1 的某些位。

    乘法指令与乘加指令

    乘法指令与乘加指令共有以下 6 条:

    • MUL 32 位乘法指令
    • MLA 32 位乘加指令
    • SMULL 64 位有符号数乘法指令
    • SMLAL 64 位有符号数乘加指令
    • UMULL 64 位无符号数乘法指令
    • UMLAL 64 位无符号数乘加指令
    MULS R0,R1,R2        ;R0 = R1 × R2,同时设置 CPSR 中的相关条件标志位
    MLAS R0,R1,R2,R3     ;R0 = R1 × R2 + R3,同时设置 CPSR 中的相关条件标志位
    SMLAL R0,R1,R2,R3    ;R0 = (R2 × R3)的低 32 位 + R0 
                         ;R1 = (R2 × R3)的高 32 位 + R1
                         ;其中,操作数 1(R2) 和操作数 2(R3) 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    均为 32 位的有符号数。

    程序状态寄存器访问指令

    — MRS 程序状态寄存器到通用寄存器的数据传送指令
    — MSR 通用寄存器到程序状态寄存器的数据传送指令

    MRS 指令
    MRS{条件}通用寄存器,程序状态寄存器(CPSR 或 SPSR)

    MRS R0,CPSR ;传送 CPSR 的内容到 R0
    MRS R0,SPSR ;传送 SPSR 的内容到 R0
    
    • 1
    • 2

    MSR 指令
    MSR{条件} 程序状态寄存器(CPSR 或 SPSR)_<域>,操作数

    MSR 指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要操作的位,32 位的程序状态寄存器可分为 4 个域:

    • 位[31:24]为条件标志位域,用 f 表示;
    • 位[23:16]为状态位域,用 s 表示;
    • 位[15:8]为扩展位域,用 x 表示;
    • 位[7:0]为控制位域,用 c 表示;

    该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在 MSR 指令中指明将要操作的域。

    MSR CPSR_c,R0 ;传送 R0 的内容到 SPSR,但仅仅修改 CPSR 中的控制位域
    
    • 1

    加载/存储指令

    LDR指令
    LDR{条件} 目的寄存器,<存储器地址>

    LDR R0,[R1,R2]        ;将存储器地址为 R1+R2 的字数据读入寄存器 R0。
    LDR R0,[R1,#8]        ;将存储器地址为 R1+8 的字数据读入寄存器 R0。
    LDR R0,[R1,R2] !      ;将存储器地址为 R1+R2 的字数据读入寄存器 R0,并将新地址 R1+R2 写入 R1。
    LDRR0,[R1,#8] !       ;将存储器地址为 R1+8 的字数据读入寄存器 R0,并将新地址 R1+8 写入 R1。
    LDRR0,[R1],R2         ;将存储器地址为 R1 的字数据读入寄存器 R0,并将新地址 R1+R2 写入 R1。
    LDRR0,[R1,R2,LSL#2]!  ;将存储器地址为 R1+R2×4 的字数据读入寄存器 R0,并将新地址 R1+R2×4 写入 R1。
    LDRR0,[R1],R2,LSL#2   ;将存储器地址为 R1 的字数据读入寄存器 R0,并将新地址 R1+R2×4 写入 R1。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    带!号的指令和[]外有寄存器的指令,都要将新地址写入R1

    批量数据加载/存储指令

    LDM(或 STM)指令

    LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{ }
    LDM(或 STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。其中,{类型} 为以下几种情况:

    类型说明
    IA每次传送后地址加 1;
    IB每次传送前地址加 1;
    DA每次传送后地址减 1;
    DB每次传送前地址减 1;
    FD满递减堆栈;
    ED空递减堆栈;
    FA满递增堆栈;
    EA空递增堆栈;
    • {!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
    • 基址寄存器不允许为 R15,寄存器列表可以为 R0~R15 的任意组合。
    • {∧ }为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。

    .word

    _undefined_instruction: .word undefined_instruction
    ldr pc, _undefined_instruction
    
    • 1
    • 2

    _undefined_instruction是一个标号,处理到这里时,as会把undefined_instruction的值按16bit的形式放在此标号处。

    ldr pc, _undefined_instruction 
    
    • 1

    就是从_undefined_instruction处取值,即undefined_instruction, 并设置到pc中

    比如:

       ldr r1, _rWTCON
    _rWTCON: .word 0x15300000
    
    • 1
    • 2

    是把地址_rWTCON上的内容放到r1,而地址_rWTCON上的内容是0x15300000。实际上就是把r1设置为0x15300000 .

    MOV和LDR的区别

    先让我们看一段汇编代码:

    LDR R0,=0x56000010 #R0 is set to be register GPBCON and is used to select pin function for Port B 
                       #in,out special function and others 
    MOV R1,#0x00004000 
    STR R1,[R0] #pin GPB7 is set to be output port
    
    • 1
    • 2
    • 3
    • 4

    以上三条汇编语句的功能是将数值0x00004000存储到以0x56000010为地址的存储单元中。
    其反汇编代码如下

       0:   e59f0044        ldr     r0, [pc, #68]   ; 0x4c
       4:   e3a01901        mov     r1, #16384      ; 0x4000
       8:   e5801000        str     r1, [r0]
       ......
       4c:   56000010        undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可见LDR R0,=0x56000010 被转换成ldr指令来执行

    再看下述代码:

    LDR R0,=0x56000000  
    MOV R1,#0x00004000
    STR R1,[R0]  
    
    • 1
    • 2
    • 3

    其反汇编代码如下

       0:   e3a00456        mov     r0, #1442840576 ; 0x56000000
       4:   e3a01901        mov     r1, #16384      ; 0x4000
       8:   e5801000        str     r1, [r0]
    
    • 1
    • 2
    • 3

    这里LDR R0,=0x56000000 被转换成mov r0, #1442840576
    也就是说LDR伪指令是根据后面的数据值来决定转换为ldr指令或MOV指令执行。

    那么同样是给R0赋值,LDR R0,=0x56000010 能否用mov r0, #0x56000010来代替呢?由此可见MOV和LDR的区别:

    mov指令后面的立即数是有限制的,这个立即数必须由一个8位的二进制数经过偶数次右移后得到才合法数据

    LDR R0,=0x56000000 被转换成mov r0, #0x56000000,其中立即数0x56000000是可以由0x56经过循环右移得到的,而0x56000010无法通过一个8位的二进制数经过偶数次右移后得到,所以无法转换成mov指令来实现。

    最后再举例如下:

    mov R0,#0x101
    mov R0,#0xFF1
    
    • 1
    • 2

    以上两条指令都不正确,因为立即数不合法。
    这样的话用MOV指令是比较麻烦的,因为有些简单的数据比较容易看出来,有些数据即不容易看出来是否是合法数据。为了解决这个问题,我们可以用LDR伪指令来实现,根据后面的立即数来决定转换为ldr指令或MOV指令执行,符合MOV指令的立即数合法性要求就转换为MOV指令,不符合的话就转换为LDR加载指令来实现。

    在Uboot源码中看到这样一条指令 MOV r1,#0xFFFFFFFF,有点费解,查看反汇编代码如下

    mvn r1,#0
    
    • 1

    原来如此。

    LDR和ADR分析

    ADR的定义为:小范围的地址读取伪指令,ADR指令将基于PC相对偏移的地址值读取到寄存器中,在编译源程序时ADR伪指令被编译器 替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,刚产生错误。

    在如上的定义中,有两个关键信息:⑴将基于PC相对偏移的地址值读取到寄存器中;⑵被编译器替换成一条合适的指令。ADR指令只能将地址值读取到寄存器中,而不能是其它的立即数,并用只能用一条指令。

    如果在汇编程序中使用ADR R1,ResetHandel语句,其中ResetHandel是汇编程序中的一个标签,此条伪指令的作用是把ResetHandel标签所在的指令地址 读取到寄存器R0中。当汇编器对此条伪指令进行编译的时候,将会编译成机器码:0xE28F100C,转换成二进制就是1110 0010 1000 1111 0001 0000 0000 1100,下面对这个机器码进行分析:

    根据上面的分析,可以看到,编译器在编译的时候把ADR伪指令编译成一个ADD R1,PC,Immediate指令,其中Immediate是一个立即数,数值是ResetHandel语句和此条伪指令之间的差值,由编译器自动算 出。由于立即数寻址的约束,这个Immediate存在一定的约束,所以会出现定义中所说的不能用一条指令实现。

    LDR说的定义为:大范围地址读取伪指令,LDR伪指令用于加载32们的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译 器替换成一条合适的指令。若加载的常数未超出MOV或者MVN的范围,刚使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入字池,并使用一 条程序相对偏移的LDR指令从文字池读出常量。

    与ARM指令的LDR相比,伪指令的LDR的参数有"="号。

    在如上的定义中,有三个关键信息:⑴用于加载32们的立即数或一个地址值到指定寄存器;⑵被编译器替换成一条合适的指令;⑶优先使用MOV或MVN指令代替该指令。

    如果使用MOV指令,那就使用立即数寻址的方式,但是立即数寻址存在一个范围白约束,所以不是所以的常数都可以使用立即数寻址白方式。当不能使用立 即数寻址方式时,就把常量放入文字池,使用一条程序相对于PC寻址的LDR指令把文字池的内容读取到寄存器中。立即数和地址值的操作方式是一样的。

    如果有伪指令:LDR R0,=0x123456。那编译器在编译该伪指令的时候将会编译成机器码:1110 0101 1001 1111 0000 0000 0001 0100,下面对这个机器码进行分析:

    由上面分析可知,伪指令LDR R0,=0x123456其实就是被编译成LDR R0,[PC,Immediate]。其中立即数0x123456被储存在一个文字池中,他的地址和指令LDR R0,[PC,Immediate]的地址之前差了Immediate。因此指令LDR R0,[PC,Immediate]就可以把立即数0x123456读取到R0中了。

    LDR 是ARM中的指令,也是伪指令。

    当用 LDR r, =imd // r 为寄存器, imd为立即数

    LDR 是一条伪指令。编译器会根据 立即数的大小,决定用 ldr 指令或者是mov或mvn指令。

    当imd能用mov或者mvn操作时,就将它翻译成一条mov或mvn指令。当imd大于mov或mvn能够操作的数时,编译器会将imd存在一个内存单元中,然后再用一条ldr指令加载这个内存单元的的值到寄存器中。

    LDR r, label 和 LDR r, =label的区别:

    LDR r, =label 会把label表示的值加载到寄存器中,而LDR r, label会把label当做地址,把label指向的地址中的值加载到寄存器中。

    譬如 label的值是 0x8000, LDR r, =label会将 0x8000加载到寄存器中,而LDR r, label则会将内存0x8000处的值加载到寄存器中。

    ADR 和 ADRL 伪指令:

    ADR 和 ADRL 伪指令用于将一个地址加载到寄存器中。

    ADR为小范围的地址读取伪指令。ADR指令将基于PC相对偏移的地址值读取到寄存器中。在汇编编译源程序时,ADR伪指令被编译器替换在一条合适 的指令,通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能使用一条指令实现,则产生错误。其能加载的地址范围,当为字节对齐 时,是-10201020,当为非字对齐时在-255255之间。

    ADRL是中等范围的地址读取指令。会被编译器翻译成两条指令。如果不能用两条指令表示,则产生错误。

    ADRL能加载的地址范围当为非字节对齐时是-64K-64K之间;当为字节对齐时是-256K-256K之间。

    三、移植u-boot到S3C2440

    简单修改使u-boot能够编译

    交叉编译环境为Fedora14和友善之臂做的交叉编译工具链

    1、修改boards.cfg,添加红色的一行

        smdk2400                     arm         arm920t     -                   samsung        s3c24x0
        smdk2410                     arm         arm920t     -                   samsung        s3c24x0
        smdk2440                     arm         arm920t     -                   samsung        s3c24x0
    
    • 1
    • 2
    • 3

    2、在$(SOURCEDIR)/u-boot-2011.06/board/samsung下,复制smdk2410 为smdk2440,修改Makefile并将文件smdk2410.c改成smdk2440.c

        make smdk2440_config
    
        make
    
    • 1
    • 2
    • 3

    3、编译成功

    4、进行下一步分析:u-boot目录结构,启动流程,代码分析,S3C2440启动初始化配置,串口配置,网口配置,文件系统烧写分析。

    点亮LED灯,the very beginning

    一般的,在初始的时候,u-boot没有任何调试手段。为了得到u-boot的内部运行状态,使能LED是一个最好的选择。这里不包括使用仿真器。

    ldr r0, =GPBUP            #设置为上拉
    ldr r1, [r0]
    orr r1, r1, #0xe0
    str r1, [r0]
    
    ldr r0, =GPBCON           #设置为IO功能
    ldr r1, [r0]
    orr r1, r1, #0x5400
    str r1, [r0]
    
    ldr r0, =GPBDAT           #输出全部为1
    ldr r1, =0xffffffff
    str r1, [r0]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    开始动手,MMU Cache/TLB/etc on/off functions

    R1_I EQU (1<<12) ;//Cache分开时,1 使能指令Cache,0 禁止使能Cache
    R1_C EQU (1<<2) ;//禁止/使能数据Cache或整个Cache,1使能 不含Cache返回0,不能禁止Cache返回1
    R1_A EQU (1<<1) ;//是否支持内存访问时地址对齐检查系统,1使能
    R1_M    EQU (1)   ;//禁止/使能MMU 1使能
    R1_iA EQU (1<<31)
    R1_nF   EQU (1<<30)
    
    ;void MMU_EnableICache(void)
       EXPORT MMU_EnableICache
    MMU_EnableICache
       mrc p15,0,r0,c1,c0,0   ;//mrc:协处理寄存器到ARM寄存器到的数据传送指令,mrc指令将协处理器寄存器中的
               ;//数据传送到ARM处理器的寄存器中,若协处理器不能成功地执行该操作,将产生未定义
               ;//异常中断,
       orr r0,r0,#R1_I
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
    
    ;void MMU_DisableICache(void)
       EXPORT MMU_DisableICache
    MMU_DisableICache
       mrc p15,0,r0,c1,c0,0
       bic r0,r0,#R1_I       ;//将立即数R1_I取反&r0,保存到r0中
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
       ;//禁止/使能指令cache
    
    ;void MMU_EnableDCache(void)
       EXPORT MMU_EnableDCache
    MMU_EnableDCache
       mrc p15,0,r0,c1,c0,0
       orr r0,r0,#R1_C
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
    
    ;void MMU_DisableDCache(void)
       EXPORT MMU_DisableDCache
    MMU_DisableDCache
       mrc p15,0,r0,c1,c0,0
       bic r0,r0,#R1_C
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
       ;//禁止/使能数据Cache     
    
    ;void MMU_EnableAlignFault(void)
       EXPORT MMU_EnableAlignFault
    MMU_EnableAlignFault
       mrc p15,0,r0,c1,c0,0
       orr r0,r0,#R1_A
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
    
    ;void MMU_DisableAlignFault(void)
       EXPORT MMU_DisableAlignFault
    MMU_DisableAlignFault
       mrc p15,0,r0,c1,c0,0
       bic r0,r0,#R1_A
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
       ;//使能/禁止存储器对齐功能
    
    ;void MMU_EnableMMU(void)
       EXPORT MMU_EnableMMU
    MMU_EnableMMU
       mrc p15,0,r0,c1,c0,0
       orr r0,r0,#R1_M
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
    
    ;void MMU_DisableMMU(void)
       EXPORT MMU_DisableMMU
    MMU_DisableMMU
       mrc p15,0,r0,c1,c0,0
       bic r0,r0,#R1_M
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
       ;//使能/禁止MMU
       ;//注意:使能/禁止MMU时
    
    ;//====================================================================================================
    ;//1、在使能MMU之前,要在内存中建立好页表,同时CP15中的各相关寄存器必须完成初始化。
    ;//2、如果使用的不是平板存储模式(物理地址和虚拟地址相等),在禁止/使能MMU时虚拟地
    ;//址和物理地址的对应关系会发生改变,这时应该清楚Cache中的当前地址变换条目。
    ;//3、如果完成禁止/使能MMU的代码的物理地址和虚拟地址不相同,则禁止/使能MMU时将造成
    ;//很大麻烦,因此强烈完成禁止/使能MMU的代码的物理地址和虚拟地址最好相同。
    
    
    ;void MMU_SetFastBusMode(void)
    ; FCLK:HCLK= 1:1
    EXPORT MMU_SetFastBusMode
    MMU_SetFastBusMode
       mrc p15,0,r0,c1,c0,0
       bic r0,r0,#R1_iA:OR:R1_nF
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
    
    ;void MMU_SetAsyncBusMode(void)
    ; FCLK:HCLK= 1:2
       EXPORT MMU_SetAsyncBusMode
    MMU_SetAsyncBusMode
       mrc p15,0,r0,c1,c0,0
       orr r0,r0,#R1_nF:OR:R1_iA
       mcr p15,0,r0,c1,c0,0
       MOV_PC_LR
        ;//C2用于保存页表的在内存中的基地址
    ;//页表中每一行对应一个虚地址页对应的实地址页的地址、该位的方位权限和该页的缓冲特性
    ;//通常把部分页表放在页表缓冲器TLB(translation lookaside buffer)中,换页表时,TLB要清空,因为。。。。
    ;//C8控制清空TLB
    ;//C10用于控制TLB中内容的锁定
    
    ;=========================
    ; Set TTBase
    ;=========================
    ;void MMU_SetTTBase(int base)
       EXPORT MMU_SetTTBase
    MMU_SetTTBase
       ;ro=TTBase
       mcr p15,0,r0,c2,c0,0
       MOV_PC_LR
    
    ;=========================
    ; Set Domain
    ;=========================
    ;void MMU_SetDomain(int domain)
       EXPORT MMU_SetDomain
    MMU_SetDomain
       ;ro=domain
       mcr p15,0,r0,c3,c0,0
       MOV_PC_LR
    
    ;=========================
    ; ICache/DCache functions
    ;=========================
    ;void MMU_InvalidateIDCache(void)
       EXPORT MMU_InvalidateIDCache
    MMU_InvalidateIDCache
       mcr p15,0,r0,c7,c7,0
       MOV_PC_LR
    
    ;void MMU_InvalidateICache(void)
       EXPORT MMU_InvalidateICache
    MMU_InvalidateICache
       mcr p15,0,r0,c7,c5,0
       MOV_PC_LR
    
    ;void MMU_InvalidateICacheMVA(U32 mva)
       EXPORT MMU_InvalidateICacheMVA
    MMU_InvalidateICacheMVA
       ;r0=mva
       mcr p15,0,r0,c7,c5,1
       MOV_PC_LR
    
    ;void MMU_PrefetchICacheMVA(U32 mva)
       EXPORT MMU_PrefetchICacheMVA
    MMU_PrefetchICacheMVA
       ;r0=mva
       mcr p15,0,r0,c7,c13,1
       MOV_PC_LR
    
    ;void MMU_InvalidateDCache(void)
       EXPORT MMU_InvalidateDCache
    MMU_InvalidateDCache
       mcr p15,0,r0,c7,c6,0
       MOV_PC_LR
    
    ;void MMU_InvalidateDCacheMVA(U32 mva)
       EXPORT MMU_InvalidateDCacheMVA
    MMU_InvalidateDCacheMVA
       ;r0=mva
       mcr p15,0,r0,c7,c6,1
       MOV_PC_LR
    
    ;void MMU_CleanDCacheMVA(U32 mva)
       EXPORT MMU_CleanDCacheMVA
    MMU_CleanDCacheMVA
       ;r0=mva
       mcr p15,0,r0,c7,c10,1
       MOV_PC_LR
    
    ;void MMU_CleanInvalidateDCacheMVA(U32 mva)
       EXPORT MMU_CleanInvalidateDCacheMVA
    MMU_CleanInvalidateDCacheMVA
       ;r0=mva
       mcr p15,0,r0,c7,c14,1
       MOV_PC_LR
    
    ;void MMU_CleanDCacheIndex(U32 index)
       EXPORT MMU_CleanDCacheIndex
    MMU_CleanDCacheIndex
       ;r0=index
       mcr p15,0,r0,c7,c10,2
       MOV_PC_LR
    
    ;void MMU_CleanInvalidateDCacheIndex(U32 index)
       EXPORT MMU_CleanInvalidateDCacheIndex
    MMU_CleanInvalidateDCacheIndex
       ;r0=index
       mcr p15,0,r0,c7,c14,2
       MOV_PC_LR
    
    ;void MMU_WaitForInterrupt(void)
       EXPORT MMU_WaitForInterrupt
    MMU_WaitForInterrupt
       mcr p15,0,r0,c7,c0,4
       MOV_PC_LR
    
    ;===============
    ; TLB functions
    ;===============
    ;voic MMU_InvalidateTLB(void)
       EXPORT MMU_InvalidateTLB
    MMU_InvalidateTLB
       mcr p15,0,r0,c8,c7,0
       MOV_PC_LR
    
    ;void MMU_InvalidateITLB(void)
       EXPORT MMU_InvalidateITLB
    MMU_InvalidateITLB
       mcr p15,0,r0,c8,c5,0
       MOV_PC_LR
    
    ;void MMU_InvalidateITLBMVA(U32 mva)
       EXPORT MMU_InvalidateITLBMVA
    MMU_InvalidateITLBMVA
       ;ro=mva
       mcr p15,0,r0,c8,c5,1
       MOV_PC_LR
    
    ;void MMU_InvalidateDTLB(void)
    EXPORT MMU_InvalidateDTLB
    MMU_InvalidateDTLB
    mcr p15,0,r0,c8,c6,0
    MOV_PC_LR
    
    ;void MMU_InvalidateDTLBMVA(U32 mva)
    EXPORT MMU_InvalidateDTLBMVA
    MMU_InvalidateDTLBMVA
    ;r0=mva
    mcr p15,0,r0,c8,c6,1
    MOV_PC_LR
    
    ;=================
    ; Cache lock down
    ;=================
    ;void MMU_SetDCacheLockdownBase(U32 base)
       EXPORT MMU_SetDCacheLockdownBase
    MMU_SetDCacheLockdownBase
       ;r0= victim & lockdown base
       mcr p15,0,r0,c9,c0,0
       MOV_PC_LR
    
    ;void MMU_SetICacheLockdownBase(U32 base)
       EXPORT MMU_SetICacheLockdownBase
    MMU_SetICacheLockdownBase
       ;r0= victim & lockdown base
       mcr p15,0,r0,c9,c0,1
       MOV_PC_LR
    
    ;=================
    ; TLB lock down
    ;=================
    ;void MMU_SetDTLBLockdown(U32 baseVictim)
       EXPORT MMU_SetDTLBLockdown
    MMU_SetDTLBLockdown
       ;r0= baseVictim
       mcr p15,0,r0,c10,c0,0
       MOV_PC_LR
    
    ;void MMU_SetITLBLockdown(U32 baseVictim)
       EXPORT MMU_SetITLBLockdown
    MMU_SetITLBLockdown
       ;r0= baseVictim
       mcr p15,0,r0,c10,c0,1
       MOV_PC_LR
    
    ;============
    ; Process ID
    ;============
    ;void MMU_SetProcessId(U32 pid)
       EXPORT MMU_SetProcessId
    MMU_SetProcessId
       ;r0= pid
       mcr p15,0,r0,c13,c0,0
       MOV_PC_LR
    
       END
    
    • 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
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285

    硬件初始化:看门狗、中断、时钟

    	/*
    	 * turn off dog, see C18P3
    	 */
    	ldr	r0, =pWTCON
    	mov	r1, #0x0
    	str	r1, [r0]
    
    	/*
    	 * C14
    	 * mask all IRQs by setting all bits in the INTMR - default
    	 */
    	mov	r1, #0xffffffff
    	ldr	r0, =INTMSK
    	str	r1, [r0]
    	ldr	r1, =0x7fff
    	ldr	r0, =INTSUBMSK
    	str	r1, [r0]
    
    	/*
    	 * FCLK:HCLK:PCLK = 1:4:8
    	 * because we will set fclk, hclk, pclk, at different speed,
    	 * the CLKDIVEN should be changed, and the C15 should be set
    	 * please see Chapter 7 Page 9
    	 */
    	ldr	r0, =CLKDIVN
    	mov	r1, #5
    	str	r1, [r0]
    	/*
    	 * orr	r0, r0, #R1_nF:OR:R1_iA
    	 * This strange thing comes from the C15 co-processor
    	 */
    	mrc	p15, 0, r0, c1, c0, 0
    	orr	r0, r0, #0xc0000000
    	mcr	p15, 0, r0, c1, c0, 0
    	/*
    	 * set mpll to 405Mhz, see C7P21
    	 */
    	ldr	r0, =MPLLCON
    	mov	r1, #0x21
    	and	r1, r1, #MPLL405
    	str	r1, [r0]
    
    • 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

    SDRAM

    《S3C2440 SDRAM内存驱动》详细介绍了SDRAM启动的方法。

    lowlevel_init:
    ldr r0, =SMRDATA
    ldr r1, _TEXT_BASE
    sub r0, r0, r1
    ldr r1, =BWSCON
    add r2, r0, #13*4
    0:
    ldr r3, [r0], #4
    str r3, [r1], #4
    cmp r2, r0
    bne 0b
    mov pc, lr
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12


    这段代码实现了U-BOOT的内存控制器部分的寄存器初始化,一共13个寄存器,对U-BOOT来最重要的就是SDRAM的初始化,显然没有这部分代码,当U-BOOT从NAND FLASH中启动的时候,START.S文件是无法完成代码的relocate的。因为SDRAM没初始化,用不了。

    要理解这段代码主要是搞清楚LDR的两种用法。

    ldr r0, =SMRDATA的作用是让r0等于U-BOOT编译时已经确定下来的SMRDATA这块内存缓冲池的起始地址。
    ldr r1, _TEXT_BASE的作用则是让r1等于_TEXT_BASE这个标号所在的内存里面的内容,也就是 TEXT_BASE。对于MINI2440开发板来说这个值等于 TEXT_BASE = 0x33F80000(\board\samsung\mini2440\config.mk)这个地址是
    由于TEXT_BASE实际的地址现在应该是FLASH的0地址(或者内部4KSRAM的0地址),所以第三条指令 sub r0, r0, r1 ,实现了计算SMRDATA当前在arm地址空间里的实际访问地址。
    接下去就是通过
    ldr r3, [r0], #4 ;从r0这个地址里取一个32bit的数据,放到r3,并将r0+4,指向下一个内存池里的数据。
    str r3, [r1], #4 ;这条指令实现了把r3里的数据赋值给r1所标示的地址。r1的地址通过下面这几句实现。以此配置完从0x48000000开始的CPU内部寄存器的值。这样SDRAM就开始工作了。以后就是正常访问0x30000000开始的地址空间了。
    #define BWSCON 0x48000000
    ldr r1, =BWSCON /* Bus Width Status Controller */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面的这个配置代码是无法在将U-BOOT直接用OPENJTAG下载到OPENJTAG中时工作的。因为当下载到内存中的时候,SDRAM的地址是随机的,至少不是从0开始的,这样得到的SMRDATA所在的地址按上面的方式是得不到的,所以需要一条在运行时地址与位置无关的指令。这就是 adr指令。下面代码就是改进后的:
    lowlevel_init:

    /* memory control configuration */
    /* make r0 relative the current location so that it */
    /* reads SMRDATA out of FLASH rather than memory ! */
    ldr r0, =SMRDATA
    ldr r1, =lowlevel_init /*编译时确定的地址*/
    sub r0, r0, r1 /* r0 = r0 -r1 , r0 = SMRDATA相对于lowlevel_init的偏移值 */
    adr r3, lowlevel_init /*位置无关*/
    add r0,r0,r3 /*r0 = r0 +r3 ,r0存放的是当前SMRDATA的实际地址*/
    ldr r1, =BWSCON /* Bus Width Status Controller */
    add r2, r0, #13*4
    0:
    ldr r3, [r0], #4
    str r3, [r1], #4
    cmp r2, r0
    bne 0b
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    至此基本上S3C2440就可以从SDRAM启动了。

    四、调试工具:openocd & openjtag

    OpenJTag是一个开源项目(包含软件和硬件)。
    硬件是一个USB接口转JTag接口的适配器。
    借助开源软件OpenOCD可以完成程序的烧写,调试等任务OpenJTag的功能:

    • 往Flash烧写程序: 支持NOR Flash 和 NAND Flash
    • 支持Windows以及Linux
    • 能够用在支持GDB调试协议的工具上: 如IAR, Eclipse
    • USB转串口功能

    OpenOCD(Open On-Chip Debugger)是一款开源的开放式片上调试软件,需要在调试适配器(如: JTAG、SWD等)的配合下可以对片上系统进行相应调试,以及在嵌入式设备上测试系统内程序或边界接扫描测试。

  • 相关阅读:
    Hive中窗口函数的基本语法和示例
    CentOS上如何配置手动和定时任务自动进行时间同步
    Eclipse - Text Editors (文本编辑器)
    K8s Docker实践二
    毕业论文写作指南
    vs2019_qt6.2.4_dcmtk3.6.7_vtk9.2.2_itk5.3_opencv4.6.0编译记录
    Transformer 中 Self-attention 的计算复杂度
    uv坐标反投影到相机坐标系
    Excel - DAO versus ADO in VBA + RDO介绍
    为什么企业需要视频会议私有部署?
  • 原文地址:https://blog.csdn.net/blueice8601/article/details/125445537